Twitter の半径数クリック以内の情報収集
使ったライブラリ
- soci
- データベースライブラリ
- picojson
- json パーサー
- boost.asio
- ネットワークライブラリ
- boost.date_time
- 日付時刻ライブラリ
ソース
#include <cassert> #include <soci.h> #include <soci-sqlite3.h> #include <unistd.h> #include <iostream> #include <sstream> #include <picojson.h> #include <boost/scoped_ptr.hpp> #include <boost/asio.hpp> #include <boost/cast.hpp> #include <boost/lexical_cast.hpp> #include <boost/date_time/posix_time/posix_time.hpp> using namespace std; using namespace SOCI; using namespace boost; using namespace boost::posix_time; using namespace boost::gregorian; namespace j = picojson; typedef asio::ip::tcp::iostream tcpstream; void interval(unsigned int t) { cout << "Interval " << t <<"[s]" << endl; sleep(t); } template<class T> j::value request(const std::string &api_path, const T &id) { interval(30); // Request Friends API cout << "Request http://twitter.com" << api_path << "/" << id << ".json" << endl; tcpstream s("twitter.com", "http"); s << "GET " << api_path << "/" << id << ".json HTTP/1.0\r\nHost: twitter.com\r\n\r\n" << flush; if (s) { // Skip Header string line; while (getline(s, line)) if (line == "\r") break; if (s) { // Parse JSON j::value v; string err = j::parse(v, s); return v; // Expect RVO } } j::value null; return null; } template<class T> bool exists(Session& sql, T id); template<> bool exists<string>(Session& sql, string screen_name) { int exists = 0; cout << "Ckeck conflict " << screen_name << endl; sql << "SELECT count(id) FROM user WHERE screen_name = :screen_name", use(screen_name), into(exists); return static_cast<bool>(exists); } template<> bool exists<unsigned long>(Session& sql, unsigned long id) { int exists = 0; cout << "Ckeck conflict " << id << endl; sql << "SELECT count(id) FROM user WHERE id = :id", use(id), into(exists); return static_cast<bool>(exists); } template<class T> bool check(const j::value& v) { // Error check if (v.is<j::object>()) { j::value error = v.get("error"); if (!error.is<j::null>()) { cout << "Oops API error!! (" << v << ")" << endl; interval(1000); return false; } } // Type check if (!v.is<T>()) { cout << "Oops Type mismatch!! (" << v << ")" << endl; interval(100); return false; } return true; } template<class T> T get(const j::value& v) { if (v.is<T>()) { return v.get<T>(); } return T(); } const unsigned int end_depth = 2; template<class T> void fetch(Session& sql, T id, unsigned int depth = 0) { while(true) { if (exists(sql, id)) { cout << "Ignore (" << id << ")" << endl; } else { // First fetch self data const j::value& value = request("/users/show", id); if (!check<j::object>(value)) continue; j::object v = value.get<j::object>(); unsigned long uid = numeric_cast<unsigned long>(get<double>(v["id"])); unsigned long friends_count = numeric_cast<unsigned long>(get<double>(v["friends_count"])); unsigned long followers_count = numeric_cast<unsigned long>(get<double>(v["followers_count"])); unsigned long favourites_count = numeric_cast<unsigned long>(get<double>(v["favourites_count"])); unsigned long statuses_count = numeric_cast<unsigned long>(get<double>(v["statuses_count"])); unsigned long created_at = (lexical_cast<ptime>(get<string>(v["created_at"])) - ptime(date(1970, Jan, 1))).total_seconds(); string time_zone = get<string>(v["time_zone"]); string screen_name = get<string>(v["screen_name"]); string profile_image_url = get<string>(v["profile_image_url"]); cout << "Insert " << screen_name << "(" << uid << ")" << endl; // Insert data sql << "INSERT INTO user VALUES(" ":id, :screen_name, :friends_count, :followers_count, :favourites_count, " ":statuses_count, :created_at, :time_zone, :profile_image_url)", use(uid), use(screen_name), use(friends_count), use(followers_count), use(favourites_count), use(statuses_count), use(created_at), use(time_zone), use(profile_image_url); } // Next fetch children data if (depth < end_depth) { cout << "Next depth" << endl; while (true) { const j::value &value = request("/friends/ids", id); if (!check<j::array>(value)) continue; j::array ids = value.get<j::array>(); for (j::array::const_iterator it = ids.begin(); it != ids.end(); ++it) { unsigned long uid = numeric_cast<unsigned long>(it->get<double>()); fetch(sql, uid, depth + 1); } break; } } break; } } int main() { // Setting ptime locale for lexical_cast locale::global(locale(locale(), new time_input_facet("%a %b %d %H:%M:%S +0000 %Y"))); try { Session sql(sqlite3, "twitter.db"); // Create table sql << "CREATE TABLE IF NOT EXISTS user (" "id INTEGER PRIMARY KEY, screen_name TEXT UNIQUE, friends_count INTEGER, followers_count INTEGER, favourites_count INTEGER, " "statuses_count INTEGER, created_at INTEGER, time_zone TEXT, profile_image_url TEXT)"; fetch(sql, std::string("amachang")); } catch(const std::exception &e) { cerr << "Error: " << e.what() << "\n"; } }
new と delete と throw で気をつけること(自分用メモ)
- delete する変数が未初期化になっている可能性を考えろ
- コンストラクタの初期化子リストで throw とか
- 初期化子リストで throw する可能性のある式を評価するな。 0 を入れておいて、コンストラクタ内で評価しろ
- または、ポインタラッパー的なものを使え。(デフォルトコンストラクタで初期化されるから)
- 未初期化変数への代入式で throw とか
- とりあえず、変数初期化子で 0 代入しとけ
- コンストラクタの初期化子リストで throw とか
- コンストラクタで例外が発生した場合にデストラクタが呼ばれないことに注意しろ
- コンストラクタ内で new して、そのままとか
- boost::shared_ptr 使え
- または、 try { /* ... */ } catch(...) { delete p; throw; } って感じで、すべての例外を catch して delete しとけ
- コンストラクタ内で new して、そのままとか
Visual Studio で exe を ldd する(依存する dll を調べる)方法
今日、教えていただいた方法。
Visual Studio には dumpbin.exe というコマンドラインツールがついているので、それを使う。
C:\bin> dumpbin /DEPENDENTS hoge.exe Microsoft (R) COFF/PE Dumper Version 9.00.30729.01 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file hoge.exe File Type: EXECUTABLE IMAGE Image has the following dependencies: KERNEL32.dll msvcrt.dll msvcrt.dll Summary 4000 .bss 6000 .data 1000 .idata 35000 .rdata 7000 .stab 27000 .stabstr CE000 .text
これは便利
Amino というライブラリを使ってみた
内容
スレッド 1 で入力を Shift_JIS -> UTF-16 変換して、 UTF-16 のデータをキューに入れる。
スレッド 2 でキューから UTF-16 のデータを取り出して、 UTF-16 -> EUC-JP 変換して出力
結果
$ g++ -O2 -lpthread -licuuc -licudata main.cpp && time ./a.out > out 0.37user 0.03system 0:00.40elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+8408minor)pagefaults 0swaps $ g++ -O2 -lpthread -licuuc -licudata main.cpp -DMULTI_THREAD && time ./a.out > out 0.34user 0.07system 0:00.30elapsed 139%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+6784minor)pagefaults 0swaps
うーん
なんか 0.1 秒早くなったけど、たぶん使いどころ違うんだろうなー。
ソース
#include <iostream> #include <unicode/uclean.h> #include <unicode/ucnv.h> #include <amino/thread.h> #include <amino/queue.h> #include <boost/tuple/tuple.hpp> const size_t CHUNKSIZE = 4096; class DecodeOp { std::istream &in_; UErrorCode uerr_; UConverter &ucnv_; int8_t charSize_; amino::LockFreeQueue<boost::tuple<UChar*, size_t> >& queue_; public: DecodeOp(std::istream &in, const std::string &charset, amino::LockFreeQueue<boost::tuple<UChar*, size_t> >& queue) : in_(in), uerr_(U_ZERO_ERROR), ucnv_(*ucnv_open(charset.c_str(), &uerr_)), charSize_(ucnv_getMinCharSize(&ucnv_)), queue_(queue) { } ~DecodeOp() { ucnv_close(&ucnv_); } void operator()() { char inBuf[CHUNKSIZE]; std::streamsize readSize = 0, consumedSize = 0, lastReadSize = 0; while (readSize = in_.rdbuf()->sgetn(inBuf, CHUNKSIZE - lastReadSize + consumedSize)) { const char* inBufBegin = inBuf; const char* inBufEnd = inBufBegin + readSize; size_t bufSize = charSize_ * readSize; UChar* buf = new UChar[bufSize]; UChar* bufBegin = buf; const UChar* bufEnd = bufBegin + bufSize; ucnv_toUnicode(&ucnv_, &bufBegin, bufEnd, &inBufBegin, inBufEnd, NULL, false, &uerr_); consumedSize = inBufBegin - inBuf; queue_.enqueue(boost::tuple<UChar*, size_t>(buf, bufBegin - buf)); lastReadSize = readSize; } size_t bufSize = charSize_ * (lastReadSize - consumedSize); UChar* buf = new UChar[bufSize]; UChar* bufBegin = buf; const UChar* bufEnd = bufBegin + bufSize; ucnv_toUnicode(&ucnv_, &bufBegin, bufEnd, NULL, NULL, NULL, true, &uerr_); queue_.enqueue(boost::tuple<UChar*, size_t>(buf, bufEnd - bufBegin)); queue_.enqueue(boost::tuple<UChar*, size_t>(NULL, 0)); } }; class EncodeOp { std::ostream &out_; UErrorCode uerr_; UConverter &ucnv_; int8_t charSize_; amino::LockFreeQueue<boost::tuple<UChar*, size_t> >& queue_; public: EncodeOp(std::ostream &out, const std::string &charset, amino::LockFreeQueue<boost::tuple<UChar*, size_t> >& queue) : out_(out), uerr_(U_ZERO_ERROR), ucnv_(*ucnv_open(charset.c_str(), &uerr_)), charSize_(ucnv_getMaxCharSize(&ucnv_)), queue_(queue) { } ~EncodeOp() { ucnv_close(&ucnv_); } void operator()() { boost::tuple<UChar*, size_t> bufData; while (true) { while (!queue_.dequeue(bufData)) { } UChar* buf = boost::get<0>(bufData); size_t bufSize = boost::get<1>(bufData); if (buf == NULL) { break; } const UChar* bufBegin = buf; const UChar* bufEnd = buf + bufSize; size_t writeSize = bufSize * charSize_; char* outBuf = new char[writeSize]; char* outBufBegin = outBuf; const char* outBufEnd = outBufBegin + writeSize; ucnv_fromUnicode(&ucnv_, &outBufBegin, outBufEnd, &bufBegin, bufEnd, NULL, false, &uerr_); out_.write(outBuf, (outBufBegin - outBuf)); assert(bufEnd == bufBegin); delete[] outBuf; delete[] buf; } } }; #include <sstream> int main() { struct Guard { UErrorCode err; Guard() : err(U_ZERO_ERROR) { u_init(&err); } ~Guard() { u_cleanup(); } } guard; amino::LockFreeQueue<boost::tuple<UChar*, size_t> > queue; std::stringstream s; int i = 1000000; while (i--) { s << "\x82\xA0"; s << "\x82\xA1"; s << "\x82\xA2"; s << "\x82\xA3"; } DecodeOp fromSjisOp(s, "Shift_JIS", queue); EncodeOp toEucOp(std::cout, "EUC-JP", queue); #ifdef MULTI_THREAD amino::Thread encoderThread(fromSjisOp), decoderThread(toEucOp); encoderThread.join(); decoderThread.join(); #else fromSjisOp(); toEucOp(); #endif }
boost::is_convertible はどうやってるか
概ね以下のような感じ
template <typename From, typename To> struct is_convertible_basic_impl { // 2 つの同名関数を作って static no_type _m_check(...); static yes_type _m_check(To); // 関数の戻り値の型を見る(どっちの関数が使われるかを見る) static bool value = sizeof( _m_check(From) ) == sizeof(yes_type); };
これで From 型のオブジェクトが To 型に代入できるかが、コンパイル時に分かるのな
sizeof の以外な使い道。
C++ で Buzztter を Growl する
はじめに
Buzztter の RSS を持ってきて、新しいキーワードを Growl に表示するものを作ってみた。
C++ でも boost::asio とか、 libxml2 とかを使うとけっこうサクっと書ける。ってこともないか。。
ソース
#include <libxml/xmlreader.h> #include <boost/asio.hpp> #include <boost/shared_ptr.hpp> #include <boost/unordered_map.hpp> #include <Growl/Growl.h> static CFTypeRef notifications[] = { CFSTR("Buzz word arrived.") }; static int notifyReading(void *in, char* buf, int len) { return static_cast<std::istream*>(in)->rdbuf()->sgetn(buf, len); } int main(int argc, char *argv[]) { Growl_Delegate delegate; InitGrowlDelegate(&delegate); delegate.applicationName = CFSTR("cppclient"); CFTypeRef keys[] = { GROWL_NOTIFICATIONS_ALL, GROWL_APP_ID }; boost::shared_ptr<const __CFArray> allNotifications( CFArrayCreate( NULL, notifications, sizeof(notifications) / sizeof(notifications[0]), &kCFTypeArrayCallBacks ), &CFRelease ); CFTypeRef values[] = { allNotifications.get(), CFSTR("com.buzzter") }; CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(CFTypeRef), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); delegate.registrationDictionary = dict; Growl_SetDelegate(&delegate); bool first = true; boost::unordered_map<std::string, bool> seen; while (true) { boost::asio::ip::tcp::iostream s("buzztter.com", "http"); s << "GET /ja/rss HTTP/1.0\r\n" << "Host: buzztter.com\r\n" << "\r\n" << std::flush; std::string line; while (std::getline(s, line)) { if (line == "\r") break; } boost::shared_ptr<xmlTextReader> reader(xmlReaderForIO(notifyReading, NULL, &s, NULL, NULL, 0), xmlFreeTextReader); while (xmlTextReaderRead(reader.get()) == 1) { if (xmlTextReaderDepth(reader.get()) == 3 && std::string("title") == reinterpret_cast<const char*>(xmlTextReaderConstName(reader.get()))) { std::ostringstream title; while (xmlTextReaderRead(reader.get()) == 1 && xmlTextReaderDepth(reader.get()) == 4) { title << std::string(reinterpret_cast<const char*>(xmlTextReaderConstValue(reader.get()))); } if (!first && !title.str().empty() && !seen[title.str()]) { boost::shared_ptr<const __CFString> word( CFStringCreateWithCString( NULL, reinterpret_cast<const char*>(title.str().c_str()), kCFStringEncodingUTF8 ), CFRelease ); Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext( CFSTR("Buzzter"), word.get(), CFSTR("Buzz word arrived."), NULL, 0, false, NULL ); } seen[title.str()] = true; } } first = false; sleep(60); } }
こんな感じでコンパイル
$ g++ -framework Growl -framework Carbon -lboost_system-mt -lxml2 main.cpp