人人都能学会的 Python 多线程指南( 四 )


文章图片

文章图片
总共需要运行十次 , 我们定义最大线程数为3 , 并在线程启动前调用acquire方法增加一个计数 , 在线程最后释放 。
此时程序一次只能启动三个线程 , 如图中所示 , 首先启动123 , 之后完成123 , 启动456 , 当第四个线程结束启动第七个线程······直到全部线程结束 。
这里我们同时使用了上一节说的线程锁来保护变量 , 用 BoundedSemaphore 锁来控制最大线程数 , 在实际写代码时就需要小心检查锁是否正确释放 , 否则就会报错!
一个真实的多线程爬虫案例
至此 , threading 模块最常见的用法就介绍完毕 , 现在让我们回到本文一开始的问题 , 有多个(以十个为例)URL需要爬取 , 既然每个页面需要执行的操作一样 , 如果等待一个页面爬取完毕再爬第二页面就太浪费时间了 。这时就可以仿照上面的思路去使用多线程加速 。
我们只需要将上面的do_something函数修改为对也面的爬取操作 , 之后的创建启动线程操作不变即可 , 代码如下
import time
import threading
import requests
import pandas as pd
from faker import Faker
from bs4 import BeautifulSoup
def craw_url(url):
global df
fake = Faker()
headers = {'User-Agent': fake.user_agent()}
r = requests.get(url, headers=headers)
soup = BeautifulSoup(r.content, 'html.parser')
review_list = soup.find_all(class_="main review-item")
for i in range(len(review_list)):
rank = review_list[i].select('span')[0].get('title')
time1 = review_list[i].select('span')[1].get('content')
title = review_list[i].select('h2>a')[0].text
df = df.append({'时间': time1,
'评分': rank,
'标题': title, }, ignore_index=True)
print("-> 爬取完成")
if __name__ == '__main__':
start = time.perf_counter()
df = pd.DataFrame(columns=['时间', '评分', '标题'])
url_list = [
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=0',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=20',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=40',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=60',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=80',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=100',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=120',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=140',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=160',
'https://movie.douban.com/subject/1652587/reviews?sort=time&start=180']
thread_list = []
for i in url_list:
thread = threading.Thread(target=craw_url, args=[i])
thread.start()
thread_list.append(thread)
for t in thread_list:
t.join()
finish = time.perf_counter()
print(f"全部任务执行完成 , 耗时 {round(finish - start,2)} 秒")
执行这段代码 , 差不多仅用了1秒就将全部内容爬取并存储到 dataframe 中 , 比同步的代码块了近十倍!如果感兴趣的话可以自己尝试一下 。
至此 , 有关 Python 多线程模块 threading 的基本用法与需要注意的几点就介绍完毕 , 如果全部认真看完的话 , 我相信你一定能照猫画虎写出第一个多线程爬虫脚本 。
当然有关 Python 多线程还有很多饱受诟病的争议(GIL) , 多线程的实现方法也远不止 threading 模块 , 例如更常见的写法是通过 concurrent.futures 模块以及多进程、协程 , 这些都留在本系列后续文章中再进一步讨论!
【人人都能学会的 Python 多线程指南】来源:搜狐