【Python】爬虫之多线程

线程

先来理解一下线程的作用,假如有一个工厂,这个工厂里面只有一条生产线,这一条生产线每周可以生产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。到这我们大致了解了如何开启多线程。

我们来理一下思路:

  1. 我们封装了一个函数get_one_year,他的作用是向目标网页发送请求,解析出我们需要的数据。
  2. 我们需要的不是一年的数据,而是很多年的,所以我们需要动态的调整URL,所以我们需要向函数传递一个参数。
  3. 需要创建一个线程池,将我们的函数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 秒
  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值