然后今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程、多线程和协程来爬取,从而对比单线程、多线程和协程在网络爬虫中的性能。

具体要爬的网址是:https://movie.douban.com/cinema/later/shenzhen/

除了要爬入口页以外还需爬取每个电影的详情页,具体要爬取的结构信息如下:


爬取测试

下面我演示使用xpath解析数据。
入口页数据读取:

import requests
from lxml import etree
import pandas as pd
import remain_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
123456789101112

结果:

<Response [200]>
1

检查一下所需数据的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
12345678910111213

结果:

然后再选择一个详情页的url进行测试,我选择了熊出没·狂野大陆这部电影,因为文本数据相对最复杂,也最具备代表性:

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

结果:

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

分析详情页结构:

文本信息都在这个位置中,下面我们直接提取这个div下面的所有文本节点:

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

结果:

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

接下来就简单了:

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

结果:

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

可以看到成功的切割出了每一项。

下面根据上面的测试基础,我们完善整体的爬虫代码:

单线程爬虫

import requests
from lxml import etree
import pandas as pd
import remain_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["电影名称"] = namefor 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:continuek, v = arrrow[k] = vrow["想看人数"] = 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)
1234567891011121314151617181920212223242526272829303132333435363738394041

结果:

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/
1234567891011121314151617181920

爬到的文件:

整体耗时:

42.5秒。

多线程爬虫

单线程的爬取耗时还是挺长的,下面看看使用多线程的爬取效率:

import requests
from lxml import etree
import pandas as pd
import re
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETEDdef 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.texturl = "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:continuek, v = arrrow[k] = vrow["url"] = urlresult.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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657

结果:


耗时8秒。

由于每个子页面都是单独的线程爬取,每个线程几乎都是同时在工作,所以最终耗时仅取决于爬取最慢的子页面。

协程异步爬虫

由于我在jupyter中运行,为了使协程能够直接在jupyter中直接运行,所以我在代码中增加了下面两行代码,在普通编辑器里面可以去掉:

import nest_asyncio
nest_asyncio.apply()
12

这个问题是因为jupyter所依赖的高版本Tornado存在bug,将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:continuek, v = arrrow[k] = vrow["url"] = urlresult.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 dfdf = asyncio.run(main())
df.to_csv("shenzhen_movie3.csv", index=False)
df
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263

结果:

耗时仅7秒,相对比多线程更快一点。

由于request库不支持协程,所以我使用了支持协程的aiohttp进行页面抓取。当然实际爬取的耗时还取绝于当时的网络,但整体来说,协程爬取会比多线程爬虫稍微快一些。

回顾

今天我向你演示了,单线程爬虫、多线程爬虫和协程爬虫。可以看到一般情况下协程爬虫速度最快,多线程爬虫略慢一点,单线程爬虫则必须上一个页面爬取完成才能继续爬取。

但协程爬虫相对来说并不是那么好编写,数据抓取无法使用request库,只能使用aiohttp。所以在实际编写爬虫时,我们一般都会使用多线程爬虫来提速,但必须注意的是网站都有ip访问频率限制,爬的过快可能会被封ip,所以一般我们在多线程提速的同时使用代理ip来并发的爬取数据。

彩蛋:xpath+pandas解析表格并提取url

我们在深圳影讯的底部能够看到一个[查看全部即将上映的影片] (https://movie.douban.com/coming)的按钮,点进去能够看到一张完整近期上映电影的列表,发现这个列表是个table标签的数据:

那就简单了,解析table我们可能压根就不需要用xpath,直接用pandas即可,但片名中包含的url地址还需解析,所以我采用xpath+pandas来解析这个网页,看看我的代码吧:

import pandas as pd
import requests
from lxml import etreeheaders = {"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
123456789101112131415

结果:

这样就能到了主页面的完整数据,再简单的处理一下即可。

近期有很多朋友通过私信咨询有关Python学习问题。为便于交流,点击蓝色自己加入讨论解答资源基地

Python爬虫:单线程、多线程和协程的爬虫性能对比!异步才是永远滴神!相关推荐

  1. Python 爬虫:单线程、多线程和协程的爬虫性能对比

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以点击下方链接自行获取 Python免费学习资料 ...

  2. Python爬虫:单线程、多线程和协程的爬虫性能对比!

    大家好,我是漆柒七7! 首先见面礼 Python学习大礼包 点击领取 然后今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程.多线程和协程来爬取,从而对比单线程.多线 ...

  3. Python爬虫:单线程、多线程和协程的爬虫性能对比

    今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程.多线程和协程来爬取,从而对比单线程.多线程和协程在网络爬虫中的性能. 具体要爬的网址是:https://movie ...

  4. Python爬虫纯干货:单线程、多线程和协程的爬虫性能对比

    今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程.多线程和协程来爬取,从而对比单线程.多线程和协程在网络爬虫中的性能. 具体要爬的网址是:https://movie ...

  5. 单线程、多线程和协程的爬虫性能对比

    作者:小小明 非常擅长解决各类复杂数据处理的逻辑,各类结构化与非结构化数据互转,字符串解析匹配等等. 至今已经帮助很多数据从业者解决工作中的实际问题,如果你在数据处理上遇到什么困难,欢迎与我交流. 豆 ...

  6. python从网址爬图片协程_Python爬虫多任务协程爬取虎牙MM图片

    查看: 4420|回复: 241 [作品展示] Python爬虫多任务协程爬取虎牙MM图片 电梯直达 发表于 2019-4-17 21:35:47 | 只看该作者 |倒序浏览 |阅读模式 马上注册,结 ...

  7. Python3多线程与协程测试

    今天简单测试了下python中的多线程与协程,之前在看python的教程中有说到python的多线程其实是在一个线程中来回切换并不会真正的去使用多核资源.如果想真正去利用多核资源,最好的办法是使用py ...

  8. 基于Python3多进程(多线程)+多协程的数据并发处理模版

    图片来源:elenabsl/Shutterstock 上一篇文章<基于Python3单进程+多线程+多协程的生产者-消费者模型示例代码>介绍了如何使用Python在单进程的情况下利用协程并 ...

  9. python 多线程和协程结合_如何让 python 处理速度翻倍?内含代码

    阿里妹导读:作为在日常开发生产中非常实用的语言,有必要掌握一些python用法,比如爬虫.网络请求等场景,很是实用.但python是单线程的,如何提高python的处理速度,是一个很重要的问题,这个问 ...

最新文章

  1. 四十二、文件的物理结构(下)
  2. 【数据结构总结1】-数据结构的自述
  3. Java:定时启动线程
  4. simulink中from与goto模块的使用
  5. Java 修饰符、运算符笔记总结
  6. 国外JAVA与IT技术网站地址
  7. 20190423面试记录
  8. python 调用rpc服务_在Django项目中对Python函数进行RPC调用的优雅方式
  9. MySQL千万级访问量架构(转)
  10. 支持有道词典单词本更新
  11. Sobel边缘检测算法
  12. 2022华为机试真题 C++ 实现【统计射击比赛成绩】
  13. 排污单位生产设施及污染治理设施用电(能)监控系统原理、作用、组成及功能
  14. cutelyst教程_01 _简介
  15. 各种炫酷的图标按钮在线制作
  16. ASO|如何开展工作
  17. Excel2013表格使用厘米为单位设置行高和列宽
  18. PUSHmall推贴S2B2B2C模式-共享电商营销平台正式版
  19. 独上高楼 望尽天涯路
  20. 学习linux第二十五天

热门文章

  1. 中国智能穿戴市场投资前景分析及供需格局研究预测报告
  2. Literature reviews of top KiTS19
  3. 文献阅读-医学超声图像分割-利用边界距离回归和像素分类网络实现超声图像肾脏自动分割
  4. TCGA中的癌症名称、简写和中文名称
  5. Nexus3忘记密码,如何登录
  6. Mac使用MySQL Workbench如何批量导入数据
  7. Android开发 Camera2开发_1_拍照功能开发
  8. Scratch编程_Day01
  9. Winform UI界面设计例程(二)主题风格切换
  10. 干货!亚马逊账号关联因素有哪些?如何有效防关联?