pyhton异步爬虫基础

一、多线程/多进程

多线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#  多线程
from threading import Thread # 线程类

#实现方法1:

def func():
for i in range(1000):
print("func", i)

if __name__ == '__main__':
t = Thread(target=func) # 创建线程并给线程安排任务
t.start() # 多线程状态为可以开始工作状态, 具体的执行时间由CPU决定
for i in range(1000):
print("main", i)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#实现方法2:

class MyThread(Thread): #
def run(self): # 固定的 -> 当线程被执行的时候, 被执行的就是run()
for i in range(1000):
print("子线程", i)

if __name__ == '__main__':
t = MyThread()
t.run() # 方法的调用了. -> 单线程????
t.start() # 开启线程

for i in range(1000):
print("主线程", i)
1
2
3
4
5
6
7
8
9
10
11
12
#传参

def func(name): # ??
for i in range(1000):
print(name, i)

if __name__ == '__main__':
t1 = Thread(target=func, args=("周杰伦",)) # 传递参数必须是元组(逗号)
t1.start()

t2 = Thread(target=func, args=("王力宏",))
t2.start()

多进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from multiprocessing import Process
from threading import Thread



def func():
for i in range(1000):
print("子进程", i)


if __name__ == '__main__':
p = Process(target=func)
p.start()
for i in range(1000):
print("主进程", i)

二、线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 1. 如何提取单个页面的数据
# 2. 上线程池,多个页面同时抓取
import requests
from lxml import etree
import csv
from concurrent.futures import ThreadPoolExecutor

f = open("data.csv", mode="w", encoding="utf-8")
csvwriter = csv.writer(f)


def download_one_page(url):
# 拿到页面源代码
resp = requests.get(url)
html = etree.HTML(resp.text)
table = html.xpath("/html/body/div[2]/div[4]/div[1]/table")[0]
# trs = table.xpath("./tr")[1:]
trs = table.xpath("./tr[position()>1]")
# 拿到每个tr
for tr in trs:
txt = tr.xpath("./td/text()")
# 对数据做简单的处理: \\ / 去掉
txt = (item.replace("\\", "").replace("/", "") for item in txt)
# 把数据存放在文件中
csvwriter.writerow(txt)
print(url, "提取完毕!")


if __name__ == '__main__':
# for i in range(1, 14870): # 效率及其低下
# download_one_page(f"http://www.xinfadi.com.cn/marketanalysis/0/list/{i}.shtml")

# 创建线程池
with ThreadPoolExecutor(50) as t:
for i in range(1, 200): # 199 * 20 = 3980
# 把下载任务提交给线程池
t.submit(download_one_page, f"http://www.xinfadi.com.cn/marketanalysis/0/list/{i}.shtml")

print("全部下载完毕!")

三、协程

​ 协程应该就是指,在单线程的条件下,合理的安排程序的I/O操作与需要使用cpu的计算操作的交互,进行任务之间的调度,使得cpu空闲机会减少,使用率上升,以达到提高程序运行效率的目的。

​ 在爬虫中,常用到的三个协程模块为:

1
2
3
import asyncio #基础协程模块
import aiohttp #协程网络请求模块
import aiofiles #协程文件读写模块

asyncio基础:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
async def func1():
print("你好啊, 我叫潘金莲")
await asyncio.sleep(3)
print("你好啊, 我叫潘金莲")

async def func2():
print("你好啊, 我叫王建国")
await asyncio.sleep(2)
print("你好啊, 我叫王建国")

async def func3():
print("你好啊, 我叫李雪琴")
await asyncio.sleep(4)
print("你好啊, 我叫李雪琴")

async def main():
# 第一种写法
f1 = func1()
await f1 # 一般await挂起操作放在协程对象前面
# 第二种写法(推荐)
tasks = [
asyncio.create_task(func1()), # py3.8以后加上asyncio.create_task()
asyncio.create_task(func2()),
asyncio.create_task(func3())
]
await asyncio.wait(tasks)

if __name__ == '__main__':
# 一次性启动多个任务(协程)
asyncio.run(main())

总结基础步骤:

  1. 创建带有async关键字的函数,函数中所有异步操作都需要await修饰;
  2. asyncio.create_task(func1())包裹后,丢进任务列表中
  3. await asyncio.wait(tasks)任务列表整体开始执行

aiohttp的基本使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import asyncio
import aiohttp

urls = [
"http://a.jpg",
"http://b.jpg",
"http://c.jpg"
]

async def aiodownload(url):
# 发送请求.
# 得到图片内容
# 保存到文件
name = url.rsplit("/", 1)[1] # 从右边切, 切一次. 得到[1]位置的内容
async with aiohttp.ClientSession() as session: # requests
async with session.get(url) as resp: # resp = requests.get()
# 请求回来了. 写入文件
# 可以自己去学习一个模块, aiofiles
with open(name, mode="wb") as f: # 创建文件
f.write(await resp.content.read()) # 读取内容是异步的. 需要await挂起, resp.text()

print(name, "Done")

async def main():
tasks = []
for url in urls:
tasks.append(aiodownload(url))

await asyncio.wait(tasks)

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

总结基本步骤:

  1. 创建带有async关键字的函数,函数中所有异步操作都需要await修饰;
  2. 使用aiohttp.ClientSession()创建session
  3. 使用session中提供的get()方法请求到url对应的资源
  4. 在已经获得的资源中读取信息也是异步的,需要使用await包裹
  5. asyncio.create_task(func1())包裹后,丢进任务列表中
  6. await asyncio.wait(tasks)任务列表整体开始执行

aiofiles的基本使用:

1
2
3
4
5
6
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
dic = await resp.json()

async with aiofiles.open(title, mode="w", encoding="utf-8") as f:
await f.write(dic['data']['novel']['content']) # 把小说内容写出

总结基本步骤:

  1. 创建带有async关键字的函数,函数中所有异步操作都需要await修饰;
  2. 使用aiohttp.ClientSession()创建session
  3. 使用session中提供的get()方法请求到url对应的资源
  4. 在已经获得的资源中读取信息也是异步的,需要使用await包裹
  5. 调用文件操作时,使用async关键字,并使用aiofiles.open替代file.open
  6. 在进行文件读写时,需要用await包裹
  7. asyncio.create_task(func1())包裹后,丢进任务列表中
  8. await asyncio.wait(tasks)任务列表整体开始执行