今日お話ししたいのは、深センの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
結果
このようにして、メインページの完全なデータを取得し、それを処理するだけで済みます。
結論
読者の皆さん、何かアイデアや利益があればコメントを残してください!