[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]
libxmlライブラリでXPathを使ってスクレイピングしました。
前の方法ではタグ内の文字列比較で絞り込んでいくためコードの階層が深くなって可読性が低かったのですが、そのような問題が解消されてとてもシンプルになりました。
これぞスクレイピングといった内容になり満足です。今後はC++でも自在に情報収集できそうです。
8年前(2015年)のStack Overflowの記事が大変参考になりました。
#include <iostream>
#include <string>
#include <vector>
#include <curl/curl.h>
#include <libxml/HTMLparser.h>
#include <libxml/xpath.h>
xmlNodeSetPtr executeXpath(xmlDocPtr &doc, xmlChar *xpath_expr) {
xmlXPathContextPtr xpath_context;
xmlXPathObjectPtr xpath_obj;
xpath_context = xmlXPathNewContext(doc);
if (xpath_context == NULL) {
cerr << "Error: unable to create new XPath context" << endl;
xmlFreeDoc(doc);
return NULL;
}
xmlNodePtr node = xmlDocGetRootElement(doc);
/* Evaluate xpath expression */
xpath_obj = xmlXPathEvalExpression(xpath_expr, xpath_context);
if (xmlXPathNodeSetIsEmpty(xpath_obj->nodesetval)) {
cerr << "Error: unable to evaluate xpath expression" << endl;
xmlXPathFreeContext(xpath_context);
xmlFreeDoc(doc);
return NULL;
}
/* Print results */
return xpath_obj->nodesetval;
}
std::string convertToString(const xmlChar* xmlString) {
if (xmlString == nullptr) {
return "";
}
return std::string(reinterpret_cast<const char*>(xmlString));
}
// コールバック関数
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* buffer) {
size_t totalSize = size * nmemb;
buffer->append((char*)contents, totalSize);
return totalSize;
}
int main() {
std::vector<std::pair<std::string, std::string>> idTitlePairs;
// URLからHTMLファイルを取り込む
std::string url = "HTMLファイルのURL";
std::string htmlBuffer;
CURL* curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &htmlBuffer);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
std::cerr << "Error: Failed to download HTML" << std::endl;
return 1;
}
} else {
std::cerr << "Error: Failed to initialize CURL" << std::endl;
return 1;
}
// htmlBufferの確認
// cout << "htmlBuffer: \n" << htmlBuffer << endl;
xmlDoc* doc = htmlReadMemory(htmlBuffer.c_str(), htmlBuffer.size(), nullptr, nullptr, HTML_PARSE_RECOVER);
if (doc) {
// XPath:/small/a, HTML:<small><a> を抽出
xmlNodeSetPtr tradObj = executeXpath(doc, (xmlChar *)"//*[name()='small']/*[name()='a']");
if (tradObj) {
for (int i = 0; i < tradObj->nodeNr; i++) {
xmlNodePtr node = tradObj->nodeTab[i];
xmlChar* id0 = xmlGetProp(node, (xmlChar*)"href");
xmlChar* title0 = xmlNodeGetContent(node);
string id = convertToString(id0);
id.erase(id.size() - 4);
string title = convertToString(title0);
idTitlePairs.push_back(std::make_pair(id, title));
xmlFree(id0);
xmlFree(title0);
}
}
xmlFreeDoc(doc);
}
for (const auto& pair : idTitlePairs) {
std::cout << "id: " << pair.first << std::endl;
std::cout << "title: " << pair.second << std::endl;
}
return 0;
}