你要的异步爬虫,这不来了吗?

爬虫程序的构成与完整链条

大家好,我是啃书君。

想必大家看了那么多的爬虫教程,都很希望知道以后的工作主要用爬虫做些什么以及自己需要掌握哪些技能吧。

今天就为大家带来爬虫程序的构成与完整链条。

爬虫程序与手机里安装的社交软件和娱乐软件不一样,但它们可能是互相关联的。你早上看到的新闻资讯以及股票走势图,都有可能是爬虫程序收集而来的。爬虫程序的核心是数据——它围绕着数据工作。

爬虫程序的链条

  • 整理需求
  • 分析目标
  • 发出网络请求
  • 文本解析
  • 数据入库
  • 数据出库
  • 搜索引擎及时展示、信息聚合、数据分析、深度学习样本、运营参考

爬虫工程师常用的库

网络请求是爬虫程序的开始,也是爬虫程序最重要的组成部分之一。首先,先来介绍网络请求库Requests,Requests是python系爬虫工程师使用最多的库,这个库以简单、易用和稳定驰名。安装方式如下:

pip install requests

安装完成之后,我们可以简单的进行HTTPGET请求体验。向http://httpbin.org/get发送请求。

可以通过"."符号访问对应的对象。

import requests


url = 'http://httpbin.org/get'
response = requests.get(url)
status_code = response.status_code	#响应状态吗
text = response.text	# 响应正文
headers = response.headers	# 响应头
print(text)
print('\n')
print(status_code, headers)

代码运行结果,如下:

{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.25.1",
    "X-Amzn-Trace-Id": "Root=1-60cc838f-122c533c14cc8b360f325fb4"
  },
  "origin": "120.236.204.4",
  "url": "http://httpbin.org/get"
}



200 {'Date': 'Fri, 18 Jun 2021 11:29:19 GMT', 'Content-Type': 'application/json', 'Content-Length': '306', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true'}

从上面的响应文本中可以发现,此时的User-Agent是python-requests/2.25.1

但是,很多时候,我们希望伪造请求头欺骗服务器的校验措施,那可以通过自定义请求头的方式达到目的。可以将User-Agent伪造成与Chrome浏览器相同的标识,对应代码如下:

headers = {
	 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
}
response = requests.get(url, headers=headers)
text = response.text	# 响应正文
print(text)

代码运行结果,如下所示:

{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",
    "X-Amzn-Trace-Id": "Root=1-60cc8616-5c121b946f9ebc6c7c30bea1"
  },
  "origin": "120.236.204.4",
  "url": "http://httpbin.org/get"
}

通过上面的运行结果可以看出,User-Agent已经成功伪装成了Chrome浏览器的标识。

有些请求可能会要求客户端携带参数,例如使用百度搜索引擎时,搜索爬虫,请求的url地址其实是https://www.baidu.com/s?wd=爬虫

从上面的url可以看出,我们向百度发起了一个get请求,并携带了一个查询参数:爬虫。

具体代码如下:

import requests


url = 'https://www.baidu.com/s'
params = {
	'wd': '爬虫'
}
headers = {
	 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
}
response = requests.get(url, headers=headers, params=params)

又如,当我们处于登陆场景时,将发出HTTPPOST请求,将用户名与密码一并发送至服务器,因此我们需要在发送网络请求时,一并将请求正文发送出去。

具体代码,如下所示:

import requests

url = 'http://httpbin.org/post'
headers = {
	 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
}
info = {'username': '啃书君', 'password': '1234567'}
response = requests.post(url, headers=headers, data=info)
text = response.text	# 响应正文
print(text)

代码运行结果,如下所示:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "password": "1234567",
    "username": "\u5543\u4e66\u541b"
  },
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "53",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",
    "X-Amzn-Trace-Id": "Root=1-60cc8a30-7fea4a67361996737caa7b97"
  },
  "json": null,
  "origin": "120.236.204.4",
  "url": "http://httpbin.org/post"
}

从上面的运行结果,可以看出,我们所提交的username与password被作为表单数据上传至服务器。

关于Requests网络请求库更多的使用方法,可以参考官方文档链接如下:

requests官方文档

https://docs.python-requests.org/en/latest/user/install/

异步网络请求库Aiohttp

首先我们要了解什么是异步,什么是同步。同步指的是第一次调用发生后,等待结果返回后才会进入下一次的调用,如果不返回结果,则一直处于等待状态,代码不会往下执行。

自从协程的概念被提出之后,python支持async、await关键字后,python的协程就开始蓬勃发展。

协程可以理解为线程的优化,又称为轻量级进程。它是一种比线程更节省资源、效率更高的系统调度机制。

协程具有这样的特点,即在开启的多个任务中,一次只执行一个,只有当前任务遭到阻塞,才会切换到下一个任务继续执行。这种机制可以实现多任务的同步,又能够成功避免线程中使用锁的复杂性,简化了开发。python中可以实现协程的模块有多个,如:asyncio、tornado或gevent。

这里以asyncio为例子,先来了解一下创建协程所用到的概念:

  • event_loop(事件循环):是一个协程处理函数的调用机制。程序会开启一个无限循环,当事件发生时调用相应的协程函数
  • async/await关键字:async用于定义一个协程,await用于挂起阻塞的异步调用接口。

简单实现协程的代码,如下所示:

# 导入asyncio模块
import asyncio


# 定义协程处理函数
async def demo(x):
	print(x)
	r = await asyncio.sleep(5)
	print(x, 'again')


# 生成协程对象,并传入demo
coroutine = demo('python.org')
loop = asyncio.get_event_loop()
try:
	# 将协程对象注册到实现事件循环对象中,并开始运行
	loop.run_until_complete(coroutine)
finally:
	# 程序结束并关闭事件循环对象
	loop.close()

运行结果,如下所示:

python.org
python.org again

上面的这段代码会打印python.org,经过5秒之后再打印python.org again

实现协程的过程,由以下几个步骤组成:

1、定义协程函数

2、生成协程对象

3、将协程对象注册到实现事件循环对象

4、关闭事件循环对象

注意:async与await是在python3.5才加入的,因此上面的代码仅仅支持python3.5+版本。

与线程相比,协程封装的内容更多,实现起来更容易。协程是多任务顺序执行,只有当前任务被挂起之后,才会切换到其他任务来执行;而线程是以CPU轮换的方式进行。

因此,两者的运行机制有着本质上的区别,这使得二者都不可被替代,在实际工作中需要根据任务需求来灵活运用线程与协程。

注意:在python3.7+之后,可以使用asyncio.run()函数调用,并执行协程,这样会更加简单。

具体代码,如下所示:

import asyncio
import time


async def  say_after(delay, what):
	await asyncio.sleep(delay)
	print(what)


async def main():
	print(f"started at {time.strftime('%X')}")

	await say_after(1, 'hello')
	await say_after(2, 'world')

	print(f"finished at {time.strftime('%X')}")


asyncio.run(main())

运行结果,如下所示:

started at 13:22:35
hello
world
finished at 13:22:38

asyncio.create_task()函数用来并发运行作为asyncio任务的多个协程。

具体代码,如下所示:

async def main():
	task1 = asyncio.create_task(say_after(1, 'hello'))
	task2 = asyncio.create_task(say_after(2, 'world'))

	print(f"started at {time.strftime('%X')}")

	await task1
	await task2


	print(f"finished at {time.strftime('%X')}")

运行结果,如下所示:

started at 14:24:20
hello
world
finished at 14:24:22

显然,程序运行的速度相比之前加快了1秒。

可等待对象

python协程属于可等待对象,因此可以在其他协程中被等待。

具体代码,如下所示:

import asyncio
async def nested():
	return 42
async def main():
	nested()
	print(await nested())
asyncio.run(main())

在第5行代码上,该程序不会由任何现象发生,一个协程对象被创建并直接调用,没有等待,因此程序将不会运行。

注意:

  • 协程函数:定义形式为async def的函数
  • 协程对象:调用协程函数的返回对象

创建任务

任务可以并行运行来调度协程。

一个协程通过asyncio.create_task()封装成一个任务,该协程会被自动调度执行。

具体代码,如下所示:

import asyncio


async def nested():
	return 42


async def main():
	task = asyncio.create_task(nested())
	print(await task)

asyncio.run(main())

有关协程与任务的更多知识,可以参考官方文档。

协程与任务

Aiohttp详解

了解完同步与异步的差异后,我们接下来将学习异步网络请求库:Aiohttp。

该网络请求库不仅具备完善的网络请求客户端功能,还支持网络服务端,因此可以用它打造一款web应用。它既支持HTTP协议,也支持webscoket协议,是爬虫工程师不可多得的工具。

安装方法:

pip install aiohttp

示例代码,如下所示:

import asyncio
import aiohttp


async def main():

    async with aiohttp.ClientSession() as session:
        async with session.get('http://python.org') as response:
            print('Status:', response.status)
            print('content-type:', response.headers['content-type'])

            html = await response.text()
            print(html)

asyncio.run(main())

这里的代码将原来的urllib.request.urlopen()改成了 async with aiohttp.ClientSession() as session。

实战

这里以当当网为例子,写一个异步爬虫,提高爬虫的效率。

from lxml import etree
import asyncio
import aiohttp


async def fetch(session, url):
    async with session.get(url) as response:
        # print(type(response.text(encoding='gb2312', errors='ignore')))

        return await response.text(encoding='gb2312', errors='ignore')


async def get_data():
    url = 'http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-24hours-0-0-1-1'
    async with aiohttp.ClientSession() as session:

        html = etree.HTML(await fetch(session, url))
        book_names = html.xpath('//div[@class="name"]/a/@title')
        return book_names


async def save_data(book_names):
    f = open('book1.txt', 'w', encoding='utf-8')
    for book_name in book_names:
        f.write(book_name)
        f.write('\n')


async def main():
    book_names = await get_data()
    print(book_names)
    await save_data(book_names)


if __name__ == '__main__':
    asyncio.run(main())

这样一来,程序的运行效率就会提高,客户端的请求发起行为和服务端返回响应的行为交替进行。

最后

看完这篇文章,觉得对你有帮助的话,可以给啃书君点个[再看],我会继续努力,和你们一起成长前进。

文章的每一个字都是我用心敲出来的,只希望对得起每一位关注我的人。

点个[再看],让我知道你们也在为自己的人生拼尽全力。

我是啃书君,一个专注于学习的人,你懂的越多,你不懂的越多,更多精彩内容,我们下期再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值