[ok]Qiitaの記事をWeb検索して、記事のURL・タイトルを抽出、記事のMarkdown形式テキストファイルを全てダウンロードする、Pythonスクリプト

検索でヒットしたQiitaの記事をダウンロードするスクリプトを試作しました。

Qiitaの記事をWeb検索して、結果から記事のURL・タイトルを抽出し、記事のテキストファイル(Markdown形式)を全てダウンロードする、Pythonスクリプトになります。

# 使用方法

末尾の実装コード「run__get_qiita_article_all.py」を使用します。
コード中の「# sec: config」部分(下記)の設定値を変えて、実行します。(Pythonのコードを実行)

「# sec: config」部分の抜粋: 
```py
# sec: config

url_qiita = "https://qiita.com/search?q=user%3Adl_from_scratch%20tag%3A%E8%AB%96%E6%96%87%E8%AA%AD%E3%81%BF&sort=created&stocked=" # 対象のURL (必須:Qiitaの検索Webページとする事、それ以外は非対応)
page_list = range(1, 3+1) # 検索結果から取得するページ範囲 (予めQiitaのWebページで検索して、取得したいページ範囲を確認しておく)
path_out = "." # 出力先
if_save_list = True # T:記事のURL・タイトルをテキストファイルに保存
if_save_md = False # T:記事のMarkdown形式テキストファイルを全てダウンロード
```
以降で各設定パラメータの説明を記載します。

### url_qiita

Qiitaの検索ページURLを指定します。例では「ユーザー：dl_from_scratch」「タグ：論文読み」「新しい順」で絞った検索結果です。Qiitaで検索した時のURLをそのまま貼り付けて使用します。

### page_list

取得する検索結果ページの範囲を指定します。range(1, 3+1)は「1〜3ページ目」を意味します。

### path_out

出力先ディレクトリを指定します。「.」はスクリプトと同じ場所に保存されることを意味します。

### if_save_list

Trueにすると、記事タイトルとURL一覧をテキストファイルで保存します。

### if_save_md

Trueにして、記事本文をMarkdown形式（.md）で保存します。


# 出力例: "qiita_url_list.txt"

当スクリプトを実行した時の出力ファイル"qiita_url_list.txt"の内容になります。
```
2024/08/30
https://qiita.com/dl_from_scratch/items/7b0bc87923c675b396ed
生成AIを用いてResNeXtの論文「Aggregated Residual Transformations for Deep Neural Networks」を読んでみた

2024/08/29
https://qiita.com/dl_from_scratch/items/910727ec6bb49dc98b5d
生成AIを用いてResNetの論文「Deep Residual Learning for Image Recognition」を読んでみた
...
```

# 出力例: qiita_mdフォルダ
当スクリプトを実行した時の出力フォルダ"qiita_md"の内容になります。
```
2024_08_30 生成AIを用いてResNeXtの論文「Aggregated Residual Transformations for Deep Neural Networks」を読んでみた.md
2024_08_29 生成AIを用いてResNetの論文「Deep Residual Learning for Image Recognition」を読んでみた.md
...
```

# 実装コード (動作確認OK)

```python:run__get_qiita_article_all.py
# Qiitaの記事をWeb検索し、検索結果の一覧から、記事のURL・タイトルを抽出
# 記事のMarkdown形式テキストファイルを全てダウンロード

# 結果: 取得可、成功。

# 出力例: "qiita_url_list.txt"
"""
2024/08/30
https://qiita.com/dl_from_scratch/items/7b0bc87923c675b396ed
生成AIを用いてResNeXtの論文「Aggregated Residual Transformations for Deep Neural Networks」を読んでみた

2024/08/29
https://qiita.com/dl_from_scratch/items/910727ec6bb49dc98b5d
生成AIを用いてResNetの論文「Deep Residual Learning for Image Recognition」を読んでみた
...
"""
# 出力例: qiita_mdフォルダには: 
"""
2024_08_30 生成AIを用いてResNeXtの論文「Aggregated Residual Transformations for Deep Neural Networks」を読んでみた.md
2024_08_29 生成AIを用いてResNetの論文「Deep Residual Learning for Image Recognition」を読んでみた.md
...
"""

import os, re, requests, time

# sec: config

url_qiita = "https://qiita.com/search?q=user%3Adl_from_scratch%20tag%3A%E8%AB%96%E6%96%87%E8%AA%AD%E3%81%BF&sort=created&stocked=" # 対象のURL (必須:Qiitaの検索Webページとする事、それ以外は非対応)
page_list = range(1, 3+1) # 検索結果から取得するページ範囲 (予めQiitaのWebページで検索して、取得したいページ範囲を確認しておく)
path_out = "." # 出力先
if_save_list = True # T:記事のURL・タイトルをテキストファイルに保存
if_save_md = False # T:記事のMarkdown形式テキストファイルを全てダウンロード

re_article = r"<time[^>]*>(.*?)</time>.*?<h3 class=\"style-1eiv6gj\">\s*<a href=\"([^\"]+)\">(.*?)</a>" # HTML内のリンクとタイトルを抽出するRE

def main():
    
    # sec: 記事のURL・タイトルを抽出
    
    list_out = []
    get_qiita_article_urls(url_qiita, page_list, list_out)

    # sec: テキストファイルに保存
    
    if if_save_list:
    
        os.makedirs(path_out, exist_ok=True)
        with open(path_out + "/qiita_url_list.txt", 'w', encoding="utf-8") as file:
            for text_date, url_arti, text_title in list_out:
                file.write(f"{text_date}\n{url_arti}\n{text_title}\n\n")

    # sec: Markdown形式を全てダウンロード
    
    if if_save_md:
        
        path_out_md = path_out + "/qiita_md"
        os.makedirs(path_out_md, exist_ok=True)
        
        for text_date, url_arti, text_title in list_out:
        
            url_md = url_arti + ".md"
            response = requests.get(url_md)
            if response.status_code == 200:
                
                markdown = response.text
                title_safe = re.sub(r"[\\/:*?\"<>|]", "_", f"{text_date} {text_title}") # タイトルに含まれるファイル名として不適切な文字を置換
                with open(path_out_md + f"/{title_safe}.md", 'w', encoding="utf-8") as file:
                    file.write(markdown)
                print("保存完了:", title_safe)
                
            else:
                print("Markdown取得に失敗:", url_arti, f"(HTTP {response.status_code})")
            
            time.sleep(1) # あまり負荷を掛け過ぎないように待機

# Qiitaの記事をWeb検索し、検索結果の一覧から、記事のURL＋タイトルを抽出
def get_qiita_article_urls(url_tar, page_list, list_out):
    
    for i_page in page_list:
        
        url_tar_i = url_tar + f"&page={i_page}"
        get_qiita_article_urls__single_page(url_tar_i, list_out)
        
        time.sleep(1) # あまり負荷を掛け過ぎないように待機

def get_qiita_article_urls__single_page(url_tar, list_out):

    # URLからHTMLソースをダウンロード
    response = requests.get(url_tar)
    response.encoding = response.apparent_encoding # 文字コードを自動判別
    text_html = response.text

    matches = re.findall(re_article, text_html)

    # リンクとテキストを出力
    for text_date, url_arti, text_title in matches:
        url_arti = "https://qiita.com" + url_arti
        text_title = text_title.strip()
        print(f"{text_date}\n{url_arti}\n{text_title}\n")
        list_out.append( (text_date, url_arti, text_title) )


# sec: entry

if __name__ == "__main__": main()

```

# コードの構造

### メイン処理（main() 関数）

以下の3ステップで処理を実行します。

記事一覧の取得
 → 指定された検索結果ページから、日付・URL・タイトルを抽出します。
　↓
URLリストの保存（任意）
 → if_save_list = True の場合、一覧ファイル qiita_url_list.txt に保存されます。
　↓
Markdownの保存（任意）
 → if_save_md = True の場合、各記事のMarkdown形式の本文をローカルに保存します。

### 関数構造

get_qiita_article_urls()
Qiitaの記事をWeb検索し、検索結果の一覧から、記事のURL＋タイトルを抽出します。

get_qiita_article_urls__single_page()
単一ページ内のHTMLから、正規表現で「投稿日・URL・タイトル」を抽出します。

# 生成AIに聞く

生成AIに聞いて、作成しました。質問・回答内容の全履歴になります。

https://chatgpt.com/share/67c58617-6920-800d-a0fa-a7dcbb8b126e

https://chatgpt.com/share/67e14117-442c-800d-83dc-370d2e9de7ff