C++でスクレイピングとWebサーバーを開発する手順を書いていきます。
# C++の開発環境構築
$ brew reinstall gcc
$ brew install cmake
$ sudo pip install conan
# プロジェクトのディレクトリ作成
$ mkdir scraping-server
$ cd scraping-server
パッケージマネージャーの conan を使うため、プロジェクトのディレクトリ配下に以下のファイルを追加します。
cmake_minimum_required(VERSION 2.8.12)
project(ScrapingServer)
add_definitions("-std=c++11")
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
add_executable(main main.cpp)
target_link_libraries(main ${CONAN_LIBS})
crowcpp-crow/1.0+3
cpr/1.9.0
gumbo-parser/0.10.1
nlohmann_json/3.11.2
cmake
CMakeLists.txt と conanfile.txt のファイルを設置したら、以下コマンドでパッケージをインストールします。
$ conan install . --build=missing
インストールしている外部パッケージは以下のものです。
まず以下の内容で main.cpp を作成します。
#include "crow.h"
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/")([](){
return "test";
});
app.port(3000).multithreaded().run();
}
その後、以下コマンドを実行して http://localhost:3000 にブラウザでアクセスすると、”test” の文字が表示されます。
# プログラムのビルド
$ cmake .
$ make
# プログラムの実行
$ ./bin/main
これで、Webサーバーの起動ができました。
ちなみにビルドと実行は以下のように続けて1行で書くこともできます。
$ cmake . && make && ./bin/main
main.cpp に以下の実装を追加します。
#include "crow.h"
/* パッケージを追加 */
#include <string>
#include <vector>
#include <cpr/cpr.h>
#include "gumbo.h"
#include <nlohmann/json.hpp>
using std::string;
using std::vector;
using json = nlohmann::json;
/* 関数を追加 */
string extract_text(GumboNode *node) {
if (node->type == GUMBO_NODE_TEXT) {
return string(node->v.text.text);
} else if (
node->type == GUMBO_NODE_ELEMENT &&
node->v.element.tag != GUMBO_TAG_SCRIPT &&
node->v.element.tag != GUMBO_TAG_STYLE
) {
string contents = "";
GumboVector *children = &node->v.element.children;
for (unsigned int i = 0; i < children->length; ++i) {
string text = extract_text((GumboNode *)children->data);
if (i != 0 && !text.empty()) {
contents.append("");
}
contents.append(text);
}
return contents;
} else {
return "";
}
}
/* 関数を追加 */
vector<GumboNode*> find_elements(GumboNode *node) {
vector<GumboNode*> elems;
GumboAttribute *attr;
if (node->type != GUMBO_NODE_ELEMENT) {
return elems;
}
if (
(attr = gumbo_get_attribute(&node->v.element.attributes, "class")) &&
strstr(attr->value, "div-col") != NULL
) {
GumboVector *div_children = &node->v.element.children;
for (int i = 0; i < div_children->length; ++i) {
auto div_child = static_cast<GumboNode *>(div_children->data);
if (
div_child->type == GUMBO_NODE_ELEMENT &&
div_child->v.element.tag == GUMBO_TAG_UL
) {
GumboVector *ul_children = &div_child->v.element.children;
for (int i = 0; i < ul_children->length; ++i) {
auto ul_child = static_cast<GumboNode *>(ul_children->data);
if (
ul_child->type == GUMBO_NODE_ELEMENT &&
ul_child->v.element.tag == GUMBO_TAG_LI
) {
elems.push_back(ul_child);
}
}
}
}
return elems;
}
GumboVector *children = &node->v.element.children;
for (int i = 0; i < children->length; ++i) {
auto found_elems = find_elements(static_cast<GumboNode *>(children->data));
elems.insert(elems.end(), found_elems.begin(), found_elems.end());
}
return elems;
}
/* 関数を追加 */
vector<string> get_items() {
// Webページの取得
string url = "https://en.wikipedia.org/wiki/List_of_programming_languages";
cpr::Response r = cpr::Get(cpr::Url{url});
string html = r.text;
// HTMLのパース
GumboOutput* output = gumbo_parse(html.c_str());
auto elems = find_elements(output->root);
vector<string> items;
for (GumboNode* elem : elems) {
items.push_back(extract_text(elem));
}
gumbo_destroy_output(&kGumboDefaultOptions, output);
return items;
}
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/")([](){
/* 関数を使用 */
auto items = get_items();
json items_json = items;
return items_json.dump();
});
app.port(3000).multithreaded().run();
}
追加実装の後、 cmake . && make && ./bin/main を再実行して http://localhost:3000 にアクセスすると、 Wikipediaのページから取得したプログラミング言語一覧の文字が表示されるようになります。
なお、HTMLのパース部分は gumbo-query という外部パッケージを使うことでさらに完結に記述できます。 今回はパッケージマネージャーの conan でインストールできなかったので gumbo-parser だけで記述しています。