Pythonクローラーの純粋な乾物:シングルスレッド、マルチスレッド、コルーチンクローラーのパフォーマンスの比較

今日お話ししたいのは、深センのDoubanで公開される映画に関する映画ニュースをクロールし、通常のシングルスレッド、マルチスレッド、コルーチンをそれぞれ使用してクロールし、シングルスレッドとマルチスレッドを比較する方法です。ネットワーク内のスレッド化されたコルーチンクローラーのパフォーマンス。

クロールされる特定のURLは次のとおりです:https://movie.douban.com/cinema/later/shenzhen/

エントリページをクロールするだけでなく、各ムービーの詳細ページもクロールする必要があります。クロールする具体的な構造情報は次のとおりです。

画像

画像

クロールテスト

以下に、xpathを使用してデータを解析する方法を示します。

読み取られたエントリページデータ:

import requests
from lxml import etree
import pandas as pd
import re

main_url = "https://movie.douban.com/cinema/later/shenzhen/"
headers = {
    "Accept-Encoding": "Gzip",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
}
r = requests.get(main_url, headers=headers)
r

結果:

<Response [200]>

必要なデータのxpathを確認してください。

画像

各映画情報がidのshowing-soon下のdivにあることがわかります。次に、内部の映画名、URL、および見たい人の数の場所を分析して、次のコードを記述できます。

html = etree.HTML(r.text)
all_movies = html.xpath("//div[@id='showing-soon']/div")
result = []
for e in all_movies:
    #  imgurl, = e.xpath(".//img/@src")
    name, = e.xpath(".//div[@class='intro']/h3/a/text()")
    url, = e.xpath(".//div[@class='intro']/h3/a/@href")
    # date, movie_type, pos = e.xpath(".//div[@class='intro']/ul/li[@class='dt']/text()")
    like_num, = e.xpath(
        ".//div[@class='intro']/ul/li[@class='dt last']/span/text()")
    result.append((name, int(like_num[:like_num.find("人")]), url))
main_df = pd.DataFrame(result, columns=["影名", "想看人数", "url"])
main_df

結果:

画像

次に、テストする詳細ページのURLを選択します。テキストデータが比較的最も複雑で最も代表的であるため、映画Bear Infested WildContinentを選択しました。

url = main_df.at[17, "url"]
url

結果:

'https://movie.douban.com/subject/34825886/'

多くの人がPythonを学び、どこから始めればよいのかわかりません。
多くの人がPythonを学び、基本的な文法を習得した後、どこから始めればよいかわかりません。
ケーススタディを行った多くの人々は、より高度な知識を学ぶ方法を知りません。
したがって、これら3つのタイプの人々のために、ビデオチュートリアル、電子書籍、およびコースのソースコードを無料で受け取ることができる優れた学習プラットフォームを提供します。
QQグループ:721195303

 

分析の詳細ページ構造:

画像

テキスト情報はこの位置にあります。以下では、このdivの下にあるすべてのテキストノードを直接抽出します。

r = requests.get(url, headers=headers)
html = etree.HTML(r.text)
movie_infos = html.xpath("//div[@id='info']//text()")
print(movie_infos)

結果:

['\n        ', '导演', ': ', '丁亮', '\n        ', '编剧', ': ', '徐芸', ' / ', '崔铁志', ' / ', '张宇', '\n        ', '主演', ': ', '张伟', ' / ', '张秉君', ' / ', '谭笑', '\n        ', '类型:', ' ', '喜剧', ' / ', '科幻', ' / ', '动画', '\n        \n        ', '制片国家/地区:', ' 中国大陆', '\n        ', '语言:', ' 汉语普通话', '\n        ', '上映日期:', ' ', '2021-02-12(中国大陆)', ' / ', '2020-08-01(上海电影节)', '\n        ', '片长:', ' ', '100分钟', '\n        ', '又名:', ' 熊出没大电影7 / 熊出没科幻大电影 / Boonie Bears: The Wild Life', '\n        ', 'IMDb链接:', ' ', 'tt11654032', '\n\n']

読みやすくするために、次のように接続します。

movie_info_txt = "".join(movie_infos)
print(movie_info_txt)

結果:

        导演: 丁亮
        编剧: 徐芸 / 崔铁志 / 张宇
        主演: 张伟 / 张秉君 / 谭笑
        类型: 喜剧 / 科幻 / 动画
        
        制片国家/地区: 中国大陆
        语言: 汉语普通话
        上映日期: 2021-02-12(中国大陆) / 2020-08-01(上海电影节)
        片长: 100分钟
        又名: 熊出没大电影7 / 熊出没科幻大电影 / Boonie Bears: The Wild Life
        IMDb链接: tt11654032

次のステップは簡単です:

row = {}
for line in re.split("[\n ]*\n[\n ]*", movie_info_txt):
    line = line.strip()
    arr = line.split(": ", maxsplit=1)
    if len(arr) != 2:
        continue
    k, v = arr
    row[k] = v
row

結果:

{'导演': '丁亮',
 '编剧': '徐芸 / 崔铁志 / 张宇',
 '主演': '张伟 / 张秉君 / 谭笑',
 '类型': '喜剧 / 科幻 / 动画',
 '制片国家/地区': '中国大陆',
 '语言': '汉语普通话',
 '上映日期': '2021-02-12(中国大陆) / 2020-08-01(上海电影节)',
 '片长': '100分钟',
 '又名': '熊出没大电影7 / 熊出没科幻大电影 / Boonie Bears: The Wild Life',
 'IMDb链接': 'tt11654032'}

各アイテムが正常に切り取られたことがわかります。

上記のテストベースに基づいて、全体的なクローラーコードを改善します。

シングルスレッドクローラー

import requests
from lxml import etree
import pandas as pd
import re

main_url = "https://movie.douban.com/cinema/later/shenzhen/"
headers = {
    "Accept-Encoding": "Gzip",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
}
r = requests.get(main_url, headers=headers)
html = etree.HTML(r.text)
all_movies = html.xpath("//div[@id='showing-soon']/div")
result = []
for e in all_movies:
    imgurl, = e.xpath(".//img/@src")
    name, = e.xpath(".//div[@class='intro']/h3/a/text()")
    url, = e.xpath(".//div[@class='intro']/h3/a/@href")
    print(url)
#     date, movie_type, pos = e.xpath(".//div[@class='intro']/ul/li[@class='dt']/text()")
    like_num, = e.xpath(
        ".//div[@class='intro']/ul/li[@class='dt last']/span/text()")
    r = requests.get(url, headers=headers)
    html = etree.HTML(r.text)
    row = {}
    row["电影名称"] = name
    for line in re.split("[\n ]*\n[\n ]*", "".join(html.xpath("//div[@id='info']//text()")).strip()):
        line = line.strip()
        arr = line.split(": ", maxsplit=1)
        if len(arr) != 2:
            continue
        k, v = arr
        row[k] = v
    row["想看人数"] = int(like_num[:like_num.find("人")])
#     row["url"] = url
#     row["图片地址"] = imgurl
#     print(row)
    result.append(row)
df = pd.DataFrame(result)
df.sort_values("想看人数", ascending=False, inplace=True)
df.to_csv("shenzhen_movie.csv", index=False)

結果:

https://movie.douban.com/subject/26752564/
https://movie.douban.com/subject/35172699/
https://movie.douban.com/subject/34992142/
https://movie.douban.com/subject/30349667/
https://movie.douban.com/subject/30283209/
https://movie.douban.com/subject/33457717/
https://movie.douban.com/subject/30487738/
https://movie.douban.com/subject/35068230/
https://movie.douban.com/subject/27039358/
https://movie.douban.com/subject/30205667/
https://movie.douban.com/subject/30476403/
https://movie.douban.com/subject/30154423/
https://movie.douban.com/subject/27619748/
https://movie.douban.com/subject/26826330/
https://movie.douban.com/subject/26935283/
https://movie.douban.com/subject/34841067/
https://movie.douban.com/subject/34880302/
https://movie.douban.com/subject/34825886/
https://movie.douban.com/subject/34779692/
https://movie.douban.com/subject/35154209/

クロールされたファイル:

画像

全体的に時間がかかる:

画像

42.5秒。

マルチスレッドクローラー

シングルスレッドのクロールには長い時間がかかります。複数のスレッドを使用したクロールの効率を見てみましょう。

import requests
from lxml import etree
import pandas as pd
import re
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED


def fetch_content(url):
    print(url)
    headers = {
        "Accept-Encoding": "Gzip",  # 使用gzip压缩传输数据让访问更快
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
    }
    r = requests.get(url, headers=headers)
    return r.text


url = "https://movie.douban.com/cinema/later/shenzhen/"
init_page = fetch_content(url)
html = etree.HTML(init_page)
all_movies = html.xpath("//div[@id='showing-soon']/div")
result = []
for e in all_movies:
#     imgurl, = e.xpath(".//img/@src")
    name, = e.xpath(".//div[@class='intro']/h3/a/text()")
    url, = e.xpath(".//div[@class='intro']/h3/a/@href")
#     date, movie_type, pos = e.xpath(".//div[@class='intro']/ul/li[@class='dt']/text()")
    like_num, = e.xpath(
        ".//div[@class='intro']/ul/li[@class='dt last']/span/text()")
    result.append((name, int(like_num[:like_num.find("人")]), url))
main_df = pd.DataFrame(result, columns=["影名", "想看人数", "url"])

max_workers = main_df.shape[0]
with ThreadPoolExecutor(max_workers=max_workers) as executor:
    future_tasks = [executor.submit(fetch_content, url) for url in main_df.url]
    wait(future_tasks, return_when=ALL_COMPLETED)
    pages = [future.result() for future in future_tasks]

result = []
for url, html_text in zip(main_df.url, pages):
    html = etree.HTML(html_text)
    row = {}
    for line in re.split("[\n ]*\n[\n ]*", "".join(html.xpath("//div[@id='info']//text()")).strip()):
        line = line.strip()
        arr = line.split(": ", maxsplit=1)
        if len(arr) != 2:
            continue
        k, v = arr
        row[k] = v
    row["url"] = url
    result.append(row)
detail_df = pd.DataFrame(result)
df = main_df.merge(detail_df, on="url")
df.drop(columns=["url"], inplace=True)
df.sort_values("想看人数", ascending=False, inplace=True)
df.to_csv("shenzhen_movie2.csv", index=False)
df

結果:

画像

8秒かかりました。

各サブページは別々のスレッドによってクロールされ、各スレッドはほぼ同時に動作しているため、最終的な消費時間は、クロールされた最も遅いサブページのみに依存します。

コルーチン非同期クローラー

私はjupyterで実行しているので、コルーチンをjupyterで直接実行できるようにするために、次の2行のコードをコードに追加しました。これは通常のエディターで削除できます。

import nest_asyncio
nest_asyncio.apply()

この問題は、jupyterが依存するTornadoの上位バージョンにバグがあるためです。Tornadoを下位バージョンにロールバックすることでも、この問題を解決できます。

以下では、コルーチンを使用して、この要求のクロールを完了します。

import aiohttp
from lxml import etree
import pandas as pd
import re
import asyncio
import nest_asyncio
nest_asyncio.apply()


async def fetch_content(url):
    print(url)
    header = {
        "Accept-Encoding": "Gzip",  # 使用gzip压缩传输数据让访问更快
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
    }
    async with aiohttp.ClientSession(
        headers=header, connector=aiohttp.TCPConnector(ssl=False)
    ) as session:
        async with session.get(url) as response:
            return await response.text()


async def main():
    url = "https://movie.douban.com/cinema/later/shenzhen/"
    init_page = await fetch_content(url)
    html = etree.HTML(init_page)
    all_movies = html.xpath("//div[@id='showing-soon']/div")
    result = []
    for e in all_movies:
        #         imgurl, = e.xpath(".//img/@src")
        name, = e.xpath(".//div[@class='intro']/h3/a/text()")
        url, = e.xpath(".//div[@class='intro']/h3/a/@href")
    #     date, movie_type, pos = e.xpath(".//div[@class='intro']/ul/li[@class='dt']/text()")
        like_num, = e.xpath(
            ".//div[@class='intro']/ul/li[@class='dt last']/span/text()")
        result.append((name, int(like_num[:like_num.find("人")]), url))
    main_df = pd.DataFrame(result, columns=["影名", "想看人数", "url"])

    tasks = [fetch_content(url) for url in main_df.url]
    pages = await asyncio.gather(*tasks)

    result = []
    for url, html_text in zip(main_df.url, pages):
        html = etree.HTML(html_text)
        row = {}
        for line in re.split("[\n ]*\n[\n ]*", "".join(html.xpath("//div[@id='info']//text()")).strip()):
            line = line.strip()
            arr = line.split(": ", maxsplit=1)
            if len(arr) != 2:
                continue
            k, v = arr
            row[k] = v
        row["url"] = url
        result.append(row)
    detail_df = pd.DataFrame(result)
    df = main_df.merge(detail_df, on="url")
    df.drop(columns=["url"], inplace=True)
    df.sort_values("想看人数", ascending=False, inplace=True)
    return df

df = asyncio.run(main())
df.to_csv("shenzhen_movie3.csv", index=False)
df

結果:

画像

所要時間はわずか7秒で、マルチスレッドよりも少し高速です。

リクエストライブラリはコルーチンをサポートしていないため、ページクロールにコルーチンをサポートするaiohttpを使用しました。もちろん、実際のクロール時間はその時点のネットワークに依存しますが、全体として、コルーチンクロールはマルチスレッドクロールよりもわずかに高速になります。

レビュー

今日は、シングルスレッドクローラー、マルチスレッドクローラー、コルーチンクローラーのデモを行いました。一般に、コルーチンクローラーが最も速く、マルチスレッドクローラーはわずかに遅く、シングルスレッドクローラーはクロールを続行する前に前のページをクロールする必要があることがわかります。

ただし、コルーチンクローラーの記述は比較的簡単ではありません。リクエストライブラリはデータキャプチャに使用できず、aiohttpのみを使用できます。したがって、実際にクローラーを作成する場合、通常はマルチスレッドクローラーを使用して速度を上げますが、WebサイトにはIPアクセス頻度の制限があることに注意する必要があります。クロールが速すぎると、IPがブロックされる可能性があるため、通常はマルチスレッド速度を使用します。同時にデータをクロールするためのプロキシIP。

イースターエッグ:xpath + pandasがテーブルを解析し、URLを抽出します

深セン映画ニュースの下部に[今後の映画をすべて表示](https://movie.douban.com/coming)ボタンが表示さます。クリックすると、最近の映画の完全なリストが表示され、これが見つかります。テーブルタグのデータ:

画像

これは簡単です。テーブルを解析するためにxpathをまったく使用する必要はなく、パンダを直接使用するだけですが、タイトルに含まれるURLアドレスを解析する必要があるため、xpath + pandasを使用してこのWebページを解析します。コードバー:

import pandas as pd
import requests
from lxml import etree

headers = {
    "Accept-Encoding": "Gzip",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
}
r = requests.get("https://movie.douban.com/coming", headers=headers)
html = etree.HTML(r.text)
table_tag = html.xpath("//table")[0]
df, = pd.read_html(etree.tostring(table_tag))
urls = table_tag.xpath(".//td[2]/a/@href")
df["url"] = urls
df

結果

画像このようにして、メインページの完全なデータを取得し、それを処理するだけで済みます。

結論

読者の皆さん、何かアイデアや利益があればコメントを残してください!

自分で作成したPython学習グループ(721195303)を引き続きお勧めします。グループ内のすべての学生がPythonを学習しています。Pythonを学習したい、または学習している場合は、ぜひ参加してください。誰もがソフトウェア開発パーティーであり、共有しています。最新のPythonの高度な資料のコピーや、2021年に自分で編集したゼロベースの教育など、随時(Pythonソフトウェア開発に関連するもののみ)の商品。高度でPythonに興味のある友人を歓迎します。
 

おすすめ

転載: blog.csdn.net/aaahtml/article/details/114299590