前言
平常做的爬虫都是用的同步方式,今天来点异步的爬虫。
使用asyncio库完成今天的事情。
既然是异步的爬虫,自然不能用普通的requests库了,而是用专用的请求库aiohttp。
知识点
1.定义协程
async def function()
想要函数能在被阻塞的时候停下该函数的执行,转而去执行另外的函数,就要将该函数定义为协程函数。
是否为协程函数可以通过
iscoroutine(function)来验证
2.await
await function()
一般放在函数的前面。当执行函数到这一行代码时,会暂停当前函数的执行,转而去执行function()函数,直到function()执行完毕或返回一个状态,才会继续执行当前函数。
3.loop
loop = asyncio.get_event_loop()
直接调用定义好的协程函数不会被执行,而是打印出generator对象。要调用携程函数,就得先获取event_loop,然后将协程函数放进loop里面,让loop去执行。
loop.run_until_complete(asyncio.ensure_future(function()))
loop.run_until_complete(function())
上面两行代码的功能都是一样的,因为run_until_complete内部会自动识别协程函数并加上asyncio.ensure_future
run_until_complete()所接受的参数只有一个,所以要想同时加入多个协程,可使用asyncio.gather()来讲多个协程合并为单个参数传入其中
tasks = [asyncio.ensure(function(), function(), function())]
tasks = asyncio.gather(*tasks)
loop.run_until_complete(tasks)
loop.close()
loop使用完后要关闭
小案例
鉴于异步的高效性,本次爬取的目标是链家的二手房数据
20页,6个维度的数据
1.使用同步技术的爬虫
import requests
import pandas as pd
import time
from lxml import etree
table = []
def fetch(url):
res = requests.get(url)
res.encoding = 'utf-8'
return res.text
def parser(html):
res = etree.HTML(html)
title = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[1]/a/text()')
place = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[2]/div/*/text()')
desc = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[3]/div/text()')
star = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[4]/text()')
price = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[6]/div[1]/span/text()')
unit_price = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[6]/div[2]/span/text()')
for i in range(0, 30):
table.append([title[i], place[2 * i] + '-' + place[2 * i + 1], desc[i], star[i], price[i], unit_price[i]])
def download(url):
parser(fetch(url))
urls = ['https://bj.lianjia.com/ershoufang/pg%d' % i for i in range(1, 20)]
print('#' * 50)
t1 = time.time()
for url in urls:
download(url)
df = pd.DataFrame(table, columns=['标题', '位置', '描述', '关注', '房价', '单价'])
# 为了避免写入到csv中中文乱码问题,此处的编码改为'utf_8_sig'
df.to_csv('tongbu1.csv', index=False, encoding="utf_8_sig")
print('使用同步,总共耗时:%s ' % (time.time() - t1))
print('#' * 50)
##################################################
使用同步,总共耗时:25.793286085128784
##################################################
2.使用异步技术的爬虫
import pandas as pd
import time
from lxml import etree
import aiohttp
import asyncio
table = []
async def fetch(session, url):
async with session.get(url) as res:
return await res.text(encoding='utf-8')
async def parser(html):
res = etree.HTML(html)
title = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[1]/a/text()')
place = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[2]/div/*/text()')
desc = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[3]/div/text()')
star = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[4]/text()')
price = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[6]/div[1]/span/text()')
unit_price = res.xpath('//*[@id="content"]/div[1]/ul/li/div[1]/div[6]/div[2]/span/text()')
for i in range(0, 30):
table.append([title[i], place[2 * i] + '-' + place[2 * i + 1], desc[i], star[i], price[i], unit_price[i]])
async def download(url):
async with aiohttp.ClientSession() as session:
html = await fetch(session, url)
await parser(html)
urls = ['https://bj.lianjia.com/ershoufang/pg%d' % i for i in range(1, 20)]
print('#' * 50)
t1 = time.time()
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(download(url)) for url in urls]
tasks = asyncio.gather(*tasks)
loop.run_until_complete(tasks)
loop.close()
df = pd.DataFrame(table, columns=['标题', '位置', '描述', '关注', '房价', '单价'])
df.to_csv('yibu1.csv', index=False, encoding="utf_8_sig")
print('使用异步,总共耗时:%s ' % (time.time() - t1))
print('#' * 50)
##################################################
使用异步,总共耗时:2.4922842979431152
##################################################
3.对比
爬取同样的页面和同样的内容,同步爬虫爬取的时间约为25.7s,而异步爬虫爬取的时间仅为2.5s,是同步的1/10。
由此可见,异步的效率还是杠杠的。
总结
效率倒是不错,但是碰见那些请求频率限制的网站,还是得乖乖停一会儿。
Python异步爬虫小案例
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法