前言

平常做的爬虫都是用的同步方式,今天来点异步的爬虫。
使用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。
由此可见,异步的效率还是杠杠的。

总结

效率倒是不错,但是碰见那些请求频率限制的网站,还是得乖乖停一会儿。