(非同期クローラー)今日、私はステーションBの弾幕を自由にクロールできます(無制限)

(非同期クローラー)ステーションBの弾幕をクロールします(無制限)

最近、ステーションBの「筆記体戦争」の新しいショーは非常に暑いです。ちょうど今、私は弾幕を降りたいと思っています。それから、弾幕について誰もが話し合っているトピックを分析して確認する時間があります。ドラマはまだ更新中なので、ここでは最初のエピソードの開始(10月3日)から今日までの弾幕だけが参照としてクロールされます。言うことはあまりありませんが、始めてください。


ページ分析

まず、ページ構造を見てみましょう。
ここに画像の説明を挿入
インターフェースに直接表示されないため、定期的なリクエストを使用してページデータを取得することは絶対に機能しません。現時点では、動的ロードが完了した後、セレンを使用してページデータを取得することを当然考えています。ページ取得を完了する前に、スクロールバーをクリックまたはプルダウンして、取得するデータをより完全にすることもできます。まず、弾幕リストを展開し、ページのソースコードを比較して、ソースコードの変更を確認します。
ここに画像の説明を挿入

弾幕を展開すると、下にスワイプすると、以前に表示した弾幕がページのソースコードのliタグに保持されません。ソースコードには固定長の弾幕のみが表示されます。この場合、セレンを使用してスライドをシミュレートします。弾幕のスリップ下部では、ページデータを取得するときに上記のデータはまだ利用できません。

考え方を変えるしかないので、ページに弾幕を載せることはできません。インターフェースから毎日の履歴弾幕を取得できます。クリックして履歴バラージを表示します。日付を選択したら、パケットキャプチャツールを使用して、historyという名前のデータパケットを見つけます。
ここに画像の説明を挿入
Request Urlにリクエストを送信することで、履歴の集中砲火を取得できます。データ型はxmlで、内容は次のとおりです。
ここに画像の説明を挿入
履歴の集中砲火はログイン後にのみ表示できるため、リクエストを開始するときにCookieもヘッダーに追加する必要があります。アイデアはそこにあります、入力を開始してください!


コードデザイン

URLリストを取得する

リクエストURL:https://api.bilibili.com/x/v2/dm/history?type = 1&oid = 241263497&date = 2020-12-04
リクエストURL:https://api.bilibili.com/x/v2/dm / history?type = 1&oid = 241263497&date = 2020-12-05

要求されたURLを比較することにより、日付のみが変更され、typeは弾幕のタイプであり、oidはエピソード番号のIDであり、各エピソードのoidが異なることを見つけるのは難しくありません。次に、URLリストを取得するためにdateの値を変更するだけで済みます。

def get_url_list():
    url_list = []
    # url模板
    url = 'https://api.bilibili.com/x/v2/dm/history?type=1&oid=241263497&date=%s'
    # 获取当前日期与开播日期间隔的天数
    interval_days = (datetime.now() - datetime(2020, 10, 3)).days
    for interval in range(interval_days, -1, -1):
        # 获取当前时间减去间隔得到的时间
        time = (datetime.now() - timedelta(days=interval)).strftime('%Y-%m-%d')
        # 将格式化的url添加到字符串中
        url_list.append(url%time)
    return url_list

応答データを取得する

ここでは、コルーチンを使用して非同期リクエストを実装します。同期ベースのリクエストリクエストライブラリを使用する場合、効率は大きく異なります。完全なデータへのアクセスを確保し、過剰なIPアクセス頻度を防ぐために、要求時にヘッダーを追加するだけでなく、プロキシIPを使用してクロールの安定性を向上させています。
リクエストプロセス中にデータが間違っていることがわかった場合は、リクエストが傍受された可能性があります。取得したレスポンスデータを印刷することもできます。図に示されている応答データは、要求が傍受されたことです。

ここに画像の説明を挿入

クロール中にリクエストが傍受されないようにするには(クローラーとして識別)、判断条件を追加します。レスポンスコードが200でない場合は、IPを変更して、レスポンスデータが正常に取得されるまでリクエストを続行します。もちろん、プロキシIPもタイムアウトを要求する場合があります。このとき、要求を続行するには、例外をキャッチしてIPを変更する必要があります。コードは以下のように表示されます。

async def get_page(url):
    headers = {
    
    
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
        'cookie': "finger=158939783; buvid3=942BBE8D-D7FB-4454-A483-E1C11686D57E155804infoc; LIVE_BUVID=AUTO2615719148192115; stardustvideo=1; rpdid=|(k|lmJ|RR~u0J'ul~umuR)ku; im_notify_type_406471840=0; CURRENT_FNVAL=80; CURRENT_QUALITY=112; _uuid=49B9C403-6E0C-AEED-F106-EB07E720DA8E77792infoc; blackside_state=0; fingerprint3=1cdc68f9c484657ac6a71df190afb556; sid=arefm2nd; fingerprint=8b41e39ee276d01b2ed70f677e090d9b; buivd_fp=942BBE8D-D7FB-4454-A483-E1C11686D57E155804infoc; fingerprint_s=2cba7720f0fa69a142e39118d29b55b5; bp_article_offset_406471840=474805593736841983; bp_t_offset_406471840=474850961478201127; PVID=1; buvid_fp=942BBE8D-D7FB-4454-A483-E1C11686D57E155804infoc; buvid_fp_plain=6406A16C-F786-4F63-B253-FE49CA5CAEA6143077infoc; bfe_id=fdfaf33a01b88dd4692ca80f00c2de7f; DedeUserID=406471840; DedeUserID__ckMd5=b61a313dfd873915; SESSDATA=9a671851%2C1625047384%2C542f9*11; bili_jct=475eaa698b53f2bc408cb650b2e0aaeb; bp_video_offset_406471840=475267392923742025"
    }
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False), trust_env=True) as session:
        while True:
            try:
                async with session.get(url=url, proxy='http://' + choice(proxy_list), headers=headers, timeout=8) as response:
                    # 更改相应数据的编码格式
                    response.encoding = 'utf-8'
                    # 遇到IO请求挂起当前任务,等IO操作完成执行之后的代码,当协程挂起时,事件循环可以去执行其他任务。
                    page_text = await response.text()
                    # 未成功获取数据时,更换ip继续请求
                    if response.status != 200:
                        continue
                    print(f"{url.split('=')[-1]}爬取完成!")
                    break
            except:
                # 捕获异常,继续请求
                continue
    return save_to_csv(page_text)

データの保存

応答データを取得した後、正則化によって必要な部分を抽出して保存できます。ここでは、その後のデータ分析を容易にするために、抽出したデータをデータフレームに保存し、簡単な処理を行った後、csvファイルとして保存します。コードは以下のように表示されます。

def save_to_csv(page_text):
    # 正则获取相关弹幕信息
    other_data = re.findall('<d p="(.*?)">', page_text)
    comment = re.findall('<d p=".*?">(.*?)</d>', page_text)

    # 创建dataframe保存数据
    df = pd.DataFrame(columns=['other_data', 'date', 'comment'])

    df['other_data'] = other_data
    df['comment'] = comment
    df['date'] = df['other_data'].str.split(',').str[4]
    df['date'] = pd.to_datetime(df['date'], unit='s')

    df.to_csv(r'C:\Users\pc\Desktop\bilibili.csv', index=False, mode='a')
    print('成功保存:' + str(len(df)) + '条')

完全なコード

# -*- coding: utf-8 -*-
'''
作者 : 丁毅
开发时间 : 2020/12/31 15:13
'''
from datetime import datetime, timedelta
from 爬虫.proxy_pool.get_IP_fromdb import proxydb
import pandas as pd
import re
import aiohttp
import asyncio
from random import sample,choice


pro = proxydb('127.0.0.1', 3306, 'root', 'password')
proxy_list = pro.get_IP_fromdb()


def get_url_list():
    url_list = []
    # url模板
    url = 'https://api.bilibili.com/x/v2/dm/history?type=1&oid=241263497&date=%s'
    # 获取当前日期与开播日期间隔的天数
    interval_days = (datetime.now() - datetime(2020, 10, 3)).days
    for interval in range(interval_days, -1, -1):
        # 获取当前时间减去间隔得到的时间
        time = (datetime.now() - timedelta(days=interval)).strftime('%Y-%m-%d')
        # 将格式化的url添加到字符串中
        url_list.append(url%time)
    return url_list


async def get_page(url):
    headers = {
    
    
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
        'cookie': "finger=158939783; buvid3=942BBE8D-D7FB-4454-A483-E1C11686D57E155804infoc; LIVE_BUVID=AUTO2615719148192115; stardustvideo=1; rpdid=|(k|lmJ|RR~u0J'ul~umuR)ku; im_notify_type_406471840=0; CURRENT_FNVAL=80; CURRENT_QUALITY=112; _uuid=49B9C403-6E0C-AEED-F106-EB07E720DA8E77792infoc; blackside_state=0; fingerprint3=1cdc68f9c484657ac6a71df190afb556; sid=arefm2nd; fingerprint=8b41e39ee276d01b2ed70f677e090d9b; buivd_fp=942BBE8D-D7FB-4454-A483-E1C11686D57E155804infoc; fingerprint_s=2cba7720f0fa69a142e39118d29b55b5; bp_article_offset_406471840=474805593736841983; bp_t_offset_406471840=474850961478201127; PVID=1; buvid_fp=942BBE8D-D7FB-4454-A483-E1C11686D57E155804infoc; buvid_fp_plain=6406A16C-F786-4F63-B253-FE49CA5CAEA6143077infoc; bfe_id=fdfaf33a01b88dd4692ca80f00c2de7f; DedeUserID=406471840; DedeUserID__ckMd5=b61a313dfd873915; SESSDATA=9a671851%2C1625047384%2C542f9*11; bili_jct=475eaa698b53f2bc408cb650b2e0aaeb; bp_video_offset_406471840=475267392923742025"
    }
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False), trust_env=True) as session:
        while True:
            try:
                async with session.get(url=url, proxy='http://' + choice(proxy_list), headers=headers, timeout=8) as response:
                    # 更改相应数据的编码格式
                    response.encoding = 'utf-8'
                    # 遇到IO请求挂起当前任务,等IO操作完成执行之后的代码,当协程挂起时,事件循环可以去执行其他任务。
                    page_text = await response.text()
                    # 未成功获取数据时,更换ip继续请求
                    if response.status != 200:
                        continue
                    print(f"{url.split('=')[-1]}爬取完成!")
                    break
            except:
                # 捕获异常,继续请求
                continue
    return save_to_csv(page_text)


def save_to_csv(page_text):
    # 正则获取相关弹幕信息
    other_data = re.findall('<d p="(.*?)">', page_text)
    comment = re.findall('<d p=".*?">(.*?)</d>', page_text)

    # 创建dataframe保存数据
    df = pd.DataFrame(columns=['other_data', 'date', 'comment'])

    df['other_data'] = other_data
    df['comment'] = comment
    df['date'] = df['other_data'].str.split(',').str[4]
    df['date'] = pd.to_datetime(df['date'], unit='s')

    df.to_csv(r'C:\Users\pc\Desktop\bilibili.csv', index=False, mode='a')
    print('成功保存:' + str(len(df)) + '条')


async def main(loop):
    # 获取url列表
    url_list = get_url_list()
    # 创建任务对象并添加岛任务列表中
    tasks = [loop.create_task(get_page(url)) for url in url_list]
    # 挂起任务列表
    await asyncio.wait(tasks)


if __name__=='__main__':
    start = datetime.now()
    # 修改事件循环的策略
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    # 创建事件循环对象
    loop = asyncio.get_event_loop()
    # 将任务添加到事件循环中并运行循环直至完成
    loop.run_until_complete(main(loop))
    # 关闭事件循环对象
    loop.close()
    print("总耗时:", datetime.now() - start)


結果のスクリーンショット:


bilibili.csvファイル

ここに画像の説明を挿入


結論:リクエストが何度も傍受され、プロキシIPが何度も変更され、IPの応答速度が少し遅い(プロキシがインターネット上でクロールされる)ため、クロール時間全体はまだ少し長いですが、リクエストよりも間違いなく速かったです。後で、データを簡単に分析し、箇条書き画面で誰もが気にかけているトピックのいくつかを見る時間があります。

おすすめ

転載: blog.csdn.net/qq_43965708/article/details/112068101