线程
先来理解一下线程的作用,假如有一个工厂,这个工厂里面只有一条生产线,这一条生产线每周可以生产10件产品,像这样的情况就可以理解为单线程。那么问题来了,如果这家工厂收到了一个生产委托,需要在一周之内生产20件产品,这个时候工厂就可以增加一条生产线,提升产能,这个情况就可以理解为多线程。
线程在爬虫中的使用
我们简单理解了一下线程,那么我们思考一下,正常写一个爬虫的思路是什么?例如下图展示的电影票房,需要爬取这些电影票房的过程是什么?
首先我们需要有目标网页的URL地址,通过requests这个Python库去请求这个地址,获取网页源代码,然后通过xpath、bs4、re等方法在网页源代码中解析出我们想要的内容。最后把这些内容写入到文件中。这样我们就可以获取一年的电影票房数据。
import requests
from lxml import etree
# 处理解析后的内容
def str_tools(lst):
if lst:
s = ''.join(lst)
return s.strip()
else:
return ''
f = open('1996.csv', 'w', encoding='utf-8')
# 抓取1996年电影票房 注意这个url无法访问
url = 'http://www.url.com/boxoffice1996'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0'
}
resp = requests.get(url, headers=headers)
tree = etree.HTML(resp.text)
trs = tree.xpath('//table/tbody/tr')[1:]
for tr in trs:
num = tr.xpath('./td[1]//text()')
year = tr.xpath('./td[2]//text()')
name = tr.xpath('./td[3]//text()')
price = tr.xpath('./td[4]//text()')
# print(num, year, name, price)
num = str_tools(num)
year = str_tools(year)
name = str_tools(name)
price = str_tools(price)
# print(num, year, name, price)
f.write(f'{num},{year},{name},{price}\n')
现在问题来了假如我想获取很多年的电影票房数据,从1996年至今的所有电影票房数据,怎么写呢?每一年的数据对应的URL是不同的,首先需要研究一下URL的规律,这里不难看出'http://www.url.com/boxoffice1996'
这个地址的改变规律是最后的面的年份,假如是2024年的数据地址就是http://www.url.com/boxoffice2024
。所以我们可以把代码封装成一个函数。把这个年份动态的传入进来。
import os.path
import requests
from lxml import etree
import time
def str_tools(lst):
if lst:
s = ''.join(lst)
return s.strip()
else:
return ''
def get_one_year(year):
path = '电影票房'
f = open(os.path.join(path, f'{year}.csv'), 'w', encoding='utf-8')
# 抓取year年电影票房
url = f'http://url/boxoffice{year}'
# 请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0'
}
# 发送请求
resp = requests.get(url, headers=headers)
# 解析页面
tree = etree.HTML(resp.text)
# 解析数据
trs = tree.xpath('//table/tbody/tr')[1:]
for tr in trs:
num = tr.xpath('./td[1]//text()')
year = tr.xpath('./td[2]//text()')
name = tr.xpath('./td[3]//text()')
price = tr.xpath('./td[4]//text()')
# print(num, year, name, price)
num = str_tools(num)
year = str_tools(year)
name = str_tools(name)
price = str_tools(price)
# print(num, year, name, price)
f.write(f'{num},{year},{name},{price}\n')
if __name__ == '__main__':
t1 = time.time()
for year in range(1996, 2025):
get_one_year(year)
print(time.time() - t1)
# 耗时 45 秒
这段程序在我的电脑上运行需要45秒才能获取到1996年到2024年所有的电影票房数据,这时候问题来了,如果我们想快一点来获取数据,怎么办呢?我们就可以考虑把程序改为多线程。我们需要了解一个东西叫线程池,我们可以把它理解为工厂,我们向页面发送请求的函数可以理解为生产线,我们多组键几个生产线,把它放到工厂里,效率不就上来了。
那么在python中如何使用线程池呢?首先需要导入from concurrent.futures import ThreadPoolExecutor
使用代码如下
from concurrent.futures import ThreadPoolExecutor
import time
def task(name):
print(f"Task {name} starting")
time.sleep(2)
print(f"Task {name} finishing")
# 创建一个ThreadPoolExecutor对象,指定线程数量为3
with ThreadPoolExecutor(max_workers=3) as executor:
# 提交任务给线程池
executor.submit(task, "A")
executor.submit(task, "B")
executor.submit(task, "C")
看到这个with ThreadPoolExecutor(max_workers=3) as executor:
代码我觉得大家应该不会陌生,是不是和文件处理with open() as f:
。文件处理中这样写是因为它可以帮助我们正确的关闭文件,它们是同样的道理,executor
就可以理解为别名。max_workers=3
这个参数是指定线程数为3,可以更改线程数,但是建议小一点,这是对网站的保护。executor.submit(task, "A")
提交任务到线程池,任务就是我们自己写的函数,其中这个A
是实参传递给函数task
的形参name
。到这我们大致了解了如何开启多线程。
我们来理一下思路:
- 我们封装了一个函数
get_one_year
,他的作用是向目标网页发送请求,解析出我们需要的数据。 - 我们需要的不是一年的数据,而是很多年的,所以我们需要动态的调整URL,所以我们需要向函数传递一个参数。
- 需要创建一个线程池,将我们的函数
get_one_year
作为任务丢进去
完整代码如下:
import os.path
import requests
from lxml import etree
from concurrent.futures import ThreadPoolExecutor
import time
def str_tools(lst):
if lst:
s = ''.join(lst)
return s.strip()
else:
return ''
def get_one_year(year):
path = '电影票房'
f = open(os.path.join(path, f'{year}.csv'), 'w', encoding='utf-8')
# 抓取year年电影票房
url = f'http://url/boxoffice{year}'
# 请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0'
}
# 发送请求
resp = requests.get(url, headers=headers)
# 解析页面
tree = etree.HTML(resp.text)
# 解析数据
trs = tree.xpath('//table/tbody/tr')[1:]
for tr in trs:
num = tr.xpath('./td[1]//text()')
year = tr.xpath('./td[2]//text()')
name = tr.xpath('./td[3]//text()')
price = tr.xpath('./td[4]//text()')
# print(num, year, name, price)
num = str_tools(num)
year = str_tools(year)
name = str_tools(name)
price = str_tools(price)
# print(num, year, name, price)
f.write(f'{num},{year},{name},{price}\n')
if __name__ == '__main__':
t1 = time.time()
with ThreadPoolExecutor(16) as t:
for y in range(1996, 2025):
t.submit(get_one_year, y)
print(time.time() - t1)
# 多线程耗时 23 秒
标签:xpath,name,Python,price,tr,爬虫,num,year,多线程
From: https://www.cnblogs.com/wangchijiao/p/18170529