Web漫画アンテナというサイトで作った自分のリストを読み取るプログラムを作ってみる

Web漫画アンテナというサイトで作った自分のリストを読み取るプログラムを作ってみる。

ChatGPTに聞いてみたところ、次のようなサンプルコードが出てきた。

libcurlでリストのwebのページを読み込んで、Gumboというライブラリでhtmlファイルをparsingして必要なものを抜き出すというものだ。

/*
 * webcomics.jp のページから
 * タイトルとリンクを抽出して titles.txt に保存
 *
 * 必要ライブラリ:
 *   libcurl
 *   gumbo-parser
 *
 * Linux:
 *   gcc main.c -lcurl -lgumbo -o scraper
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <gumbo.h>

typedef struct {
    char *memory;
    size_t size;
} MemoryBlock;

static size_t write_callback(void *contents,
                             size_t size,
                             size_t nmemb,
                             void *userp)
{
    size_t realsize = size * nmemb;

    MemoryBlock *mem = (MemoryBlock *)userp;

    char *ptr = realloc(mem->memory,
                        mem->size + realsize + 1);

    if (!ptr)
        return 0;

    mem->memory = ptr;

    memcpy(&(mem->memory[mem->size]),
           contents,
           realsize);

    mem->size += realsize;
    mem->memory[mem->size] = '\0';

    return realsize;
}

static const char* get_attribute(
    GumboElement *element,
    const char *name)
{
    GumboAttribute *attr =
        gumbo_get_attribute(
            &element->attributes,
            name);

    return attr ? attr->value : NULL;
}

static void extract_text(
    GumboNode *node,
    char *buffer,
    size_t maxlen)
{
    if (node->type == GUMBO_NODE_TEXT)
    {
        strncat(buffer,
                node->v.text.text,
                maxlen - strlen(buffer) - 1);
    }
    else if (node->type == GUMBO_NODE_ELEMENT)
    {
        GumboVector *children =
            &node->v.element.children;

        for (unsigned int i = 0;
             i < children->length;
             i++)
        {
            extract_text(
                children->data[i],
                buffer,
                maxlen);
        }
    }
}

static void search_links(
    GumboNode *node,
    FILE *fp)
{
    if (node->type != GUMBO_NODE_ELEMENT)
        return;

    GumboElement *element =
        &node->v.element;

    if (element->tag == GUMBO_TAG_A)
    {
        const char *href =
            get_attribute(element, "href");

        if (href)
        {
            char title[2048] = {0};

            extract_text(node,
                         title,
                         sizeof(title));

            if (strlen(title) > 0)
            {
                fprintf(fp,
                        "%s\t%s\n",
                        title,
                        href);
            }
        }
    }

    GumboVector *children =
        &element->children;

    for (unsigned int i = 0;
         i < children->length;
         i++)
    {
        search_links(children->data[i],
                     fp);
    }
}

int main(void)
{
    const char *url =
        "https://webcomics.jp/list/dm8hk";

    CURL *curl;
    CURLcode res;

    MemoryBlock chunk;
    chunk.memory = malloc(1);
    chunk.size = 0;

    curl_global_init(CURL_GLOBAL_DEFAULT);

    curl = curl_easy_init();

    if (!curl)
    {
        fprintf(stderr,
                "curl init failed\n");
        return 1;
    }

    curl_easy_setopt(curl,
                     CURLOPT_URL,
                     url);

    curl_easy_setopt(curl,
                     CURLOPT_FOLLOWLOCATION,
                     1L);

    curl_easy_setopt(curl,
                     CURLOPT_WRITEFUNCTION,
                     write_callback);

    curl_easy_setopt(curl,
                     CURLOPT_WRITEDATA,
                     &chunk);

    curl_easy_setopt(curl,
                     CURLOPT_USERAGENT,
                     "Mozilla/5.0");

    res = curl_easy_perform(curl);

    if (res != CURLE_OK)
    {
        fprintf(stderr,
                "curl error: %s\n",
                curl_easy_strerror(res));

        curl_easy_cleanup(curl);
        free(chunk.memory);
        return 1;
    }

    FILE *fp = fopen("titles.txt", "w");

    if (!fp)
    {
        perror("fopen");
        return 1;
    }

    GumboOutput *output =
        gumbo_parse(chunk.memory);

    search_links(output->root, fp);

    gumbo_destroy_output(
        &kGumboDefaultOptions,
        output);

    fclose(fp);

    curl_easy_cleanup(curl);
    curl_global_cleanup();

    free(chunk.memory);

    printf("saved to titles.txt\n");

    return 0;
}

これくらいならAIにプログラムを作って貰わなくても、curlコマンドを実行して、結果のリストのhtmlをファイルに保存して、そのhtmlファイルからタグ情報を手がかりにデータを抜き出すプログラムを自分で書いたほうが早そうだ。

htmlファイルは、だいたいこんな形式の繰り返しになっている。(ニコニコ漫画のほうのリスト)

	<div class="entry" data-comic-no="162038">
		<div class="entry-thumb">
			<a href="https://manga.nicovideo.jp/comic/66641" target="_blank">
				<img src="./kako-list-1 - Web漫画アンテナ_files/162038.jpg" width="100" height="100" loading="lazy" alt="近畿地方のある場所について">
			</a>
		</div>

					<div class="entry-date">
				17時間前			</div>
		
		<div class="entry-title ellipsis1">
			<a href="https://manga.nicovideo.jp/comic/66641" target="_blank">
				近畿地方のある場所について			</a>
		</div>

		
					<div class="entry-summary ellipsis2">
				行方不明になった友人・小沢。オカルト雑誌の編集者であった彼は、失踪する前に「●●●●●」という地域にまつわる怪談を取材していた。過去の雑誌記事からの引用、読者からのおかしな体験談、インターネット掲示板...			</div>
		
		<div class="entry-site">
			<a href="https://webcomics.jp/seiga">
				ニコニコ漫画			</a>
		</div>

		<div class="entry-footer">
															<span class="entry-comment">
									<span class="hover-tip">
						<span class="hover-tip-popup hover-tip-popup-top-left">
							<span class="hover-tip-popup-block">
								コメントはまだありません
							</span>
						</span>
						コメント
					</span>
							</span>
			<span class="entry-detail">
				<a href="https://webcomics.jp/seiga/66641">詳細・更新情報</a>
			</span>
		</div>
	</div>

“entry-thumb”という単語を探して、その次の行からhttpsで始まるURL文字列を取り出す処理を書けばよい。割と簡単そうだ。

そして、URLの末尾に”new”というのを付けると最新話へのリンクになる。
さらに漫画のタイトルと、更新の時間情報も取ることができる。

まずはこれらをファイルとして出力し、そのあと、実際に漫画を閲覧するプログラムは別で書いてみようと思う。

そうやって分解して短めのソフトを作っていけば、1つの大きいソフトとして漫画更新チェック&閲覧アプリを作るよりも簡単に作れるだろう。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Time limit is exhausted. Please reload CAPTCHA.

3 × = 3