Pythonクローラーの実践事例 - とある小説のクローリング

序文

最近、小説を読むのが好きな友人が増えていますが、毎回特定のサイトにアクセスして読むのは非常に面倒です。特に田舎の故郷に帰ると電波が悪くてデータネットワークが使えないことがありますが、WIFIはありますか?なので、小説を事前にダウンロードできればOKです。

準備する

実は、準備するものは何もありません。クローラーがよく使用するモジュールは問題ありません。現在使用されているモジュールは、request、json、re の 3 つだけです。

トピックを開始します

パケットをキャプチャする

実際、クローラーのステップは非常に固定されています。最初のステップは、パッケージを手に取り、小説をランダムに選択し、無料トライアルをクリックすることです。小説の内容が表示されます。ディレクトリをクリックすると、アドレス バーのアドレスが変更されていないことがわかります。そのため、Ajax によって要求され、Fetch/XHR でデータ パケットを直接検索します。

 予想通り、全章のjsonデータは以下のデータパッケージ内にありました。

レイヤーごとに展開し、各インデックスの下でこれらのコンテンツを見つけます。

 

次に、これらのデータと各チャプターの関係を調べますが、3 つのチャプターのアドレスバーを観察すると、以前作成したミュージック クローラーとまったく同じパターンが簡単に見つかりました。

第2章

第三章

第四章 

 このアドレスの前の文字はすべて変更されておらず、変更されたのは最後のデータだけであることは誰でも簡単にわかります。このデータは、注意深く観察すると、上記のデータ パケットのチャプター インデックスの下にある id に対応する値であることが簡単にわかります。このようにして、各章のアドレスが解決されます。

次に、各章の詳細ページに移動して、この小説のテキスト コンテンツを取得する方法を見つける必要があります。

「すべて」の最初のパケットに小説のテキストコンテンツを見つけました 

 この時点で、立ち止まって振り返ることができます。これまでにどのような有益な情報が得られましたか?

1.各章のアドレスはURLです

2. 各章の詳細ページの小説テキストの内容はどこにありますか?

次に、この URL に基づいて小説のテキスト コンテンツを取得するだけです。

タイプコード

まず、目次ページのデータパケットを要求し、目次ページのデータパケットを通じて各章のURLを取得します。ここで注意が必要なのは、このウェブサイトはある程度のクロール対策が施されており、リクエストヘッダーを持っていない場合、データをリクエストすることができません。

 

リクエストURLとリクエストヘッダーは上図のようになり、データをリクエストすることができます。bookIdに注目してください。使用する場合は、このbookIdを直接入力すれば正常に動作します。先ほど示した各章URLの最後から2番目のデータ、つまり最後から2番目の数字の列です。それが小説IDです。もう 1 つの点は、実際にはリクエスト ヘッダーに暗号化されたフィールドが多数あることです。これらのフィールドが追加されるとデータはリクエストされなくなります。スクリーンショットは撮りませんでした。リクエストコードは以下のとおりです。

bookId = input('bookId:')
url = 'https://www.qidian.com/ajax/book/category?bookId={}&_csrfToken=mLzoQhBuJ9pPDdAm8VpBuvMFXCgQQAlhFrC9TuvU'.format(bookId)

headers = {
    'Accept':'application/json, text/plain, */*',
    'Accept-Encoding':'gzip, deflate, br',
    'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
    'Connection':'keep-alive',
    'Cookie':'_csrfToken=mLzoQhBuJ9pPDdAm8VpBuvMFXCgQQAlhFrC9TuvU; newstatisticUUID=1692151465_166373733; Hm_lvt_f00f67093ce2f38f215010b699629083=1692151469; fu=1381548093; _yep_uuid=ad752dda-9748-ea50-e98f-865f3b8bb989; _gid=GA1.2.1689446406.1692151469; supportwebp=true; supportWebp=true; trkf=1; qdrs=0%7C3%7C0%7C0%7C1; navWelfareTime=1692152350314; showSectionCommentGuide=1; qdgd=1; rcr=1036370336; bc=1036370336; e1=%7B%22pid%22%3A%22qd_P_mycenter%22%2C%22eid%22%3A%22qd_H_mall_bottomaddownload%22%2C%22l7%22%3A%22hddl%22%7D; e2=%7B%22l6%22%3A%22%22%2C%22pid%22%3A%22qd_p_qidian%22%2C%22eid%22%3A%22qd_A16%22%2C%22l1%22%3A3%7D; lrbc=1036370336%7C749524263%7C0; _gat_gtag_UA_199934072_2=1; traffic_utm_referer=https%3A%2F%2Flink.csdn.net%2F; Hm_lpvt_f00f67093ce2f38f215010b699629083=1692155018; _ga_FZMMH98S83=GS1.1.1692151469.1.1.1692155018.0.0.0; _ga_PFYW0QLV3P=GS1.1.1692151469.1.1.1692155018.0.0.0; _ga=GA1.2.2023067070.1692151469',
    'Host':'www.qidian.com',
    'Referer':'https://www.qidian.com/chapter/1023826840/573073457/',
    'sec-ch-ua':'"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
    'sec-ch-ua-mobile':'?0',
    'sec-ch-ua-platform':'"Windows"',
    'Sec-Fetch-Dest':'empty',
    'Sec-Fetch-Mode':'cors',
    'Sec-Fetch-Site':'same-origin',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42',
    'X-D':'0'
}

response = requests.get(url=url, headers=headers)

もう一つ、テキストを直接出力すると文字化けが起こるため、エンコード形式を手動でutf-8に設定しています。

response.encoding = 'utf-8'

次に、正規表現を使用してテキスト内の ID 値と、もちろん各章の名前を抽出します。

ex = '{"uuid":.*?,"cN":"(.*?)","uT":.*?,"cnt":.*?,"cU":.*?,"id":(.*?),"sS":.*?}'
data = re.findall(ex, response.text, re.S)

この時点で、各章の URL と各章の名前がわかります。

データ抽出

次に章ごとにデータを抽出していきます。もちろん、最初のステップは各章の URL を作成し、次に各章の URL をリクエストすることです。次に、正規表現を使用して新規テキストを抽出し続けます。コードは次のとおりです。

chapterUrlList = []
titleList = []
bookBodyList = []
print("正在获取书籍内容,请耐心等待哦!")
for i in data:
    # 获取每一章的章名
    title = i[0]
    titleList.append(title)
    # 获取每一章的链接地址
    chapterUrl = 'https://www.qidian.com/chapter/{}/'.format(bookId) + i[-1] + '/'
    chapterUrlList.append(chapterUrl)
    # 用正则表达式获取每一章的内容
    book = requests.get(url = chapterUrl)
    ex1 = '<main .*?><p>(.*?)</p></main>'
    bookBody = re.findall(ex1, book.text, re.S)
    # 用replace替换掉小说文本中的一些特殊符号
    body = bookBody[0].replace('\u3000\u3000', '\n')
    body = body.replace('<p>', '')
    body = body.replace('</p>', '')
    # 将文本保存到列表中
    bookBodyList.append(body)
# 获取小说名字和小说作者等信息
book_Info = requests.get(url = chapterUrl)
ex2 = '<script id="vite-plugin-ssr_pageContext" type="application/json">(.*?)</script>'
book_Info = re.findall(ex2, book_Info.text, re.S)[0]
json_data = json.loads(book_Info)
print(type(json_data))
bookName = json_data['pageContext']['pageProps']['pageData']['bookInfo']['bookName']
authorName = json_data['pageContext']['pageProps']['pageData']['bookInfo']['authorName']
print("正在保存,马上就好哦!")

最後のステップは、要求された小説テキストを保存することです。

with open('书名:'+bookName + '作者' + authorName + '.txt', 'w') as f:
    for j in range(0, len(titleList)):
       f.write(titleList[j])
       f.write(':')
       f.write(chapterUrlList[j])
       f.write(bookBodyList[j])
print("保存成功!")

 これで完了です!

予防

1. Webサイトはある程度逆クロールされていますが、リクエストヘッダーに暗号化されたパラメータがいくつかあり、すべて追加されている場合はデータのリクエストができず、何も追加されていない場合はデータのリクエストができません。

2. 実は、最も面倒な点はデータの抽出であり、新規テキストコンテンツの中には HTML 構文記号が多く含まれているものがあるため、それらをクリーンアップする必要があります。

3. 正規表現は実際には一致しにくい、あるいは私の学習能力が低いのかもしれません。レスポンスからHTMLの標準コードを直接コピーしているため、改行など表示できない記号が含まれています。マッチングする場合、これらの制限された文字はマッチングできません。

完全なコードをリソースに掲載しますので、ご興味があればダウンロードしてください。

学習目的のみに使用し、違法な目的で使用しないでください。

おすすめ

転載: blog.csdn.net/qq_64241302/article/details/132319135