Python实战:用多线程和多进程打造高效爬虫

🍋引言

在网络爬虫的世界里,效率是关键。为了快速地获取大量数据,我们需要运用一些高级技巧,如多线程和多进程。在本篇博客中,我们将学习如何使用Python的多线程和多进程来构建一个高效的网络爬虫,以便更快速地获取目标网站上的信息。

🍋为什么要使用多线程和多进程?

在单线程爬虫中,我们按照顺序一个个页面地下载和解析数据。这在小型网站上可能没有问题,但在处理大规模数据时会变得非常缓慢。多线程和多进程可以帮助我们同时处理多个页面,从而提高爬虫的效率。

  • 多线程:在一个进程内,多个线程可以并发执行,共享相同的内存空间。这意味着它们可以更快速地完成任务,但需要小心线程安全问题。

  • 多进程:多个进程是独立的,每个进程都有自己的内存空间。这可以避免线程安全问题,但在创建和管理进程时会产生额外的开销。

🍋线程的常用方法

  • threading.Thread(target, args=(), kwargs={}):创建一个线程对象,用于执行指定的目标函数。
    target:要执行的目标函数。
    args:目标函数的位置参数,以元组形式传递。
    kwargs:目标函数的关键字参数,以字典形式传递。

  • thread.start():启动线程,使其开始执行目标函数。

  • thread.join(timeout=None):等待线程完成执行。可选的timeout参数用于指定最长等待时间,如果超时,将继续执行主线程。

  • thread.is_alive():检查线程是否在运行。如果线程仍在执行中,返回True;否则返回False。

  • threading.current_thread():返回当前线程对象,可以用于获取当前线程的信息。

  • threading.active_count():返回当前活动线程的数量。

  • threading.enumerate():返回当前所有活动线程的列表。

  • threading.Thread.getName()和threading.Thread.setName(name):获取和设置线程的名称。

  • threading.Thread.isDaemon()和threading.Thread.setDaemon(daemonic):获取和设置线程的守护状态。守护线程在主线程结束时会被强制终止。

  • threading.Thread.ident:获取线程的唯一标识符。

  • threading.Lock():创建一个互斥锁对象,用于实现线程同步。

  • lock.acquire():获取锁,进入临界区。

  • lock.release():释放锁,退出临界区。

  • .sleep() 方法是线程对象(threading.Thread)中的一个非常常用的方法之一,它用于使线程暂停执行一段指定的时间,但是用到的库是time

🍋线程锁(也称为互斥锁或简称锁)

用于线程间的同步,主要有以下两个目的:

  • 防止竞争条件(Race Condition): 竞争条件指的是多个线程在同时访问共享资源时,由于执行顺序不确定而导致的不确定性和错误。当多个线程尝试同时修改共享数据时,可能会导致数据不一致性和错误结果。线程锁可以防止竞争条件,使得只有一个线程能够访问共享资源,其他线程需要等待锁释放。

  • 确保数据一致性: 在多线程环境下,当多个线程同时访问和修改共享数据时,可能会导致数据的不一致性。使用锁可以确保在任何时候只有一个线程能够访问和修改共享数据,从而确保数据的一致性。

这里可以举个例子:

当我们在银行取钱的时候,如果没有线程锁的保护,一大堆人同时取钱,那么余额就不好计算了。所以在多个线程同时进行存款和取款操作,但由于线程锁的保护,这些操作不会导致账户数据的混乱或错误。最后,我们打印出最终的账户余额,以确保数据一致性。

🍋小案例

这里我们通过一个小案例感受一下多线程的魅力吧~

import time
import threading
import requests
from bs4 import BeautifulSoup

urls = []
for j in range(1, 5):
    url = f'https://example.com/page{j}'
    urls.append(url)
# 存储字典
results = {}
lock = threading.Lock()
def func(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    title = soup.title.string
    # 使用线程锁确保字典操作的线程安全
    with lock:
        results[url] = title
# 创建线程列表
threads = []
start = time.time()
# 创建并启动线程
for url in urls:
    thread = threading.Thread(target=func, args=(url,))
    threads.append(thread)
    thread.start()

# 等待所有线程完成
for thread in threads:
    thread.join()
stop = time.time()
# 打印爬取结果
for url, title in results.items():
    print(f'URL: {url}, Title: {title}')
print(stop-start)

这里我们用测试网页中的title在这里插入图片描述

在这个示例中,我们定义了一个包含多个网页url的列表 urls,然后创建了多个线程来并发地爬取这些网页的标题。每个线程使用 requests 库发送请求,解析网页内容,提取标题,并将结果存储在一个共享的 results 字典中。为了确保字典操作的线程安全,我们使用了一个线程锁 lock。最后用time测试了一次时间。

🍋实战—手办网

先将之前相关爬取手办网放在下方

# 导入模块
import requests
from bs4 import BeautifulSoup
import time
import threading
# 定义url和请求头
_headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
                "Cookie": "utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105"
            }

"""
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=1
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=2
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=3
"""
start = time.time()
# 获取前五页的url
urls = []
for i in range(1,5):
    url ='https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page={}'.format(i)
    urls.append(url)
items = []
for d_url in urls:
    # 发送请求
    response = requests.get(d_url, headers=_headers)
    content = response.content.decode('utf8')
    # 实例化对象
    soup = BeautifulSoup(content, 'lxml')
    # 名称
    data = soup.find_all('ul',class_="hpoi-glyphicons-list")
    for i in data:
        data_1 = i.find_all('li')
        for j in data_1:
            data_2 = j.find_all('div',class_="hpoi-detail-grid-right")
            for k in data_2:
                title = k.find_all('a')[0].string
                changshang = k.find_all('span')[0].text[3:]
                chuhe = k.find_all('span')[1].text[3:]
                price = k.find_all('span')[2].text[3:]
                data_3 = {
                    "名称": title,
                    "厂商":changshang,
                    "出荷":chuhe,
                    "价位":price
                }
                items.append(data_3)
print(items)

stop = time.time()
print(stop-start)

运行结果如下
在这里插入图片描述

之后我们使用多线程进行实现

# 导入模块
import requests
from bs4 import BeautifulSoup
import time
import threading
# 定义url和请求头
_headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
                "Cookie": "utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105"
            }

"""
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=1
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=2
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=3
"""
start = time.time()
# 获取前五页的url

items = []
def shouban(url):
# for d_url in urls:
    # 发送请求
    response = requests.get(url, headers=_headers)
    content = response.content.decode('utf8')
    # 实例化对象
    soup = BeautifulSoup(content, 'lxml')
    # 名称
    data = soup.find_all('ul',class_="hpoi-glyphicons-list")
    for i in data:
        data_1 = i.find_all('li')
        for j in data_1:
            data_2 = j.find_all('div',class_="hpoi-detail-grid-right")
            for k in data_2:
                title = k.find_all('a')[0].string
                changshang = k.find_all('span')[0].text[3:]
                chuhe = k.find_all('span')[1].text[3:]
                price = k.find_all('span')[2].text[3:]
                data_3 = {
                    "名称": title,
                    "厂商":changshang,
                    "出荷":chuhe,
                    "价位":price
                }
                items.append(data_3)

urls = []
t_list = []
for i in range(1,5):
    url =f'https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page={i}'
    t1 = threading.Thread(target=shouban, args=(url,))
    t1.start()
    t_list.append(t1)

for i in t_list:
    i.join()

stop = time.time()
print(items)
print(stop-start)

运行结果如下
在这里插入图片描述
注意:可以适当添加sleep,防止被封,如果爬取大量数据,多线程表现的会更明显一点

🍋总结

通过使用多线程和多进程,我们可以显著提高网络爬虫的效率,更快地获取大量数据。然而,要小心线程安全问题和进程管理的开销。在实际项目中,还需要考虑异常处理、数据存储等更多细节,感谢看到结尾的小伙伴,感谢您的支持!

请添加图片描述

挑战与创造都是很痛苦的,但是很充实。

  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论
尹成Python27天从入门到实战项目!全课665节讲解详细易操作,助力实战项目能力成长,轻松、简单、易懂!day1  python入门与学习方法精确思维与用到才能记忆深刻课程简介课前介绍计算机简介与硬盘概念内存作用计算机小结编程语言简介操作系统简介python版本简介切换python版本修改环境变量交互式编程两种风格python3代码保存必须是utf-8使用IDE了解注释段落打印三引号注释多行三个单引号python代码要严格对齐中文输入字符错误简单数学表达式代码风格三种错误风格系统执行指令运行结果乱码交互式编程绘图绘制线段与圆形改变颜色绘制奥运五环小结习题day1plus python  常量变量数据类型 常量与变量区别标识符关键字输入输出id求地址type求类型python变量是地址赋值可以改变类型python主要数据类型复数数据类型自适应变长整数intdel作用连续赋值交互对称赋值字符串转化与输入输出编程wmv多行拆分多行归并成一行规范用的常量算术运算符科学计数法与数字越界优先级结合性赋值运算符数据类型转换四舍五入数据提取时间处理作业day2   python字符串与绘图复习变量None变量参与运算没有赋值的情况不会改变绘制立方体绘制五角星时间计算强化实现贷款计算连续输入多个字符串加法与绘图实现显示线段长度数据函数使用快速查阅函数帮助字符串三种风格字符与编号中文字符编号问题字符编号用途统一码转义字符意义转义字符用于字符串print高级用法字符串加法与转换字符串小结python中所有的数据都是对象字符串格式化了解进制图形绘制重点作业以及学习认知方法day3   python运算符与ifelse逻辑语句第一天上午回顾第二天下午回顾1第二天回顾2.7习题解答2.11习题解答2.26作业KaliLinux简介3.4实现五边形面积计算3.7随机字符生成3.8实数误差与四舍五入3.18解答与特殊字符if简介与空格的作用if处理的是逻辑表达式关系运算符针对数值字符串的关系运算符if自动转化Bool类型随机数if缺点且运算符或运算符取反运算符短路效应身份运算符运算符优先级结合性概览pass语句潜台词翻译系统None用途指令翻译系统嵌套嵌套与elif对比以及注意细节习题解答绘图作业与重点day4  语音合成与逻辑循环回顾语音合成简单理解while控制次数WhileElseWhileElse实战whileFloat回顾与ifelse表达式赌博概率分类设计方法-一元二次方程嵌套求有序3个数闰年计算矩形区域随机数ifelse回文数判断10进制转化16进制16进制转十进制暴力穷举无限循环指令循环同步异步黑科技读写内存读写内存无限循环监测重点与作业day5  复杂逻辑循环while与whileelse编程风格循环控制图形绘制while猜数字输入输出重定向while习题分析可以退出的命令系统for初步简介跑分流程结合for循环小结forfor嵌套forfor分析break中断循环contiune结束本次循环双层循环的break与continue分析break与contiue小结一闪一闪亮晶晶循环控制大小与位置对角线绘制圆形口顺时针运动循环等价作业与小结day6  函数实战回顾.算学费输入数据求最大精确划分思维解决最大次大交换数据实现按行显示围棋棋盘绘制国际象棋绘制为什么要用函数函数的四种类型函数的一般形式必须用函数的理由-哥德巴赫函数的本质就是地址函数变量意义函数变量用途装饰器设计模式包含模式函数概念None函数默认参数与返回值作为参数名称参数与位置参数参数副本机制副本练习多个返回值全局变量与局部变量函数内部引用全局变量函数可以嵌套nolocal处理函数嵌套变量函数nonlocalnonlocal总结开房数据查询day7      函数实战与常用数据结构函数的两种类型函数的本质再分析函数地址切换切换功能ifelse配合地址筛选函数收费函数调用过程函数基本小结return小结位置参数与默认参数混合参数填充混合填充错误情况lambda匿名函数可变长函数字符串常识普及字符串特点字符串不可以修改内部字符list列表list用途tuple简介集合运算字典概念in与notin运算符构造表达式转换数据类型系统小结拓展查询作业day8   字符串实战简单回顾eval与exec简介字符串的简单规范字符串的截取字符串赋值规范字符串转义字符字符串常见运算符字符串格式化字符串格式化强化字符串编码简介dir与help查看str字符串函数第一个大写以及字符串居中显示打印金字塔count统计字符串出现个数字符串编码注意事项解码失败判断字符串以什么为结束tab键转换find函数与rfind函数day9   字符串高级简单回顾..eval与exec简介字符串的简单规范字符串的截取字符串赋值规范字符串转义字符字符串常见运算符字符串格式化字符串格式化强化字符串编码简介dir与help查看str字符串函数第一个大写以及字符串居中显示打印金字塔count统计字符串出现个数字符串编码注意事项解码失败判断字符串以什么为结束tab键转换find函数与rfind函数index函数与rindex函数字符串判断字符串间隔求长度以及左右填充字符串大小写取出最大最小字符替换去除空格简单切割换行切割判断开头大小写切换以及填充0翻译表.字符串模板小结day10  字符串集合与语音识别实战回顾lset初始化风格set增加与去重set删除元素遍历set类型转化set无法操作单个元素的赋值set常见方法set常见运算符set关系运算符判断包含set集合判断frozenset字典的基本定义循环字典判断在不在实现插入与更新字典删除字典详解字典与set无法嵌套迭代器列表生成式生成器生成器节省内存用于循环生成器yield作用生成器简介测试游戏简介游戏初级修改键盘模拟语音控制游戏实现鼠标模拟代码规范函数简化重用列表与元组小结作业day11  阶段综合练习昨日回顾.堆栈函数模块引用系统模块引用与错误列表生成式的若干方法生成矩阵str与reprpython把输入输出设备当作文件处理写入中文写入英文读取中文读取英文读取错误写入错误处理文件缓存区文件读取若干种方式for循环按行读取数据文件指针seektell数据结构与文件的交互数据查询并保存结果数据查询小结与数据day12downwithoutdata  数据处理开发大数据指令数据清洗的概念初级数据清洗密码排序次数统计QQ密码概率分布QQ号码分类器.开房数据清洗..区域划分省份划分年龄月份划分日划分day12up  数据实战简单回顾12系统默认编码中文编码常见错误字符串切割抓取邮箱抓取用户名与密码排序密码文件统计次数排序.统计文件密码次数密码次数排序day13withoutdata  数据分类与list深入地区分类详解字典分类器数据简单归并数据如何切割数据加密数据解密指令执行判断文件夹编程小结列表归并-删除列表归并-下标小结与作业day14down   面向对象编程与深浅拷贝面向对象与过程的差异self用途动态绑定增加属性方法重载运算符的概念有名对象与匿名对象重载运算符的返回值重载运算符多个类型类的拷贝是浅复制深浅拷贝函数调用参数副本原理函数调用可以改变list的元素不可以改变list指向对象当作参数的细节私有变量私有变量用于设计权限私有方法用于设计权限私有变量本质用类的方式读取文件行数密码次数归并数据处理方法简介今日小结作业wmvday14up  暴力穷举与类回顾....排列组合暴力生成穷举密码密码破解必破生成器密码破解小结为啥使用类-代码重用类的一般形式详解self构造函数与析构函数构造函数初始化类的属性最简洁的UI设定窗体的位置以及大小类的实际用途day15down  面向对象数据搜索实战输入处理文本编辑器表格数据树状显示数据搜索可视化第一步实现查询窗体数据搜索可视化第二步实现搜索数据数据搜索可视化第三步实现显示窗体数据可视化第四步显示与搜索串联数据可视化终结作业小结day15up  面向对象设计与图像界面上设计一个类发短信类的方法设计一下短信发送邮件类的设计-设计发邮件类的多文件引用腾讯营销类的设计消息循环消息回掉函数机制lambda按下按钮标签显示文本文本输入框listcombobox选择列表day16up  面向对象与图形界面开发下设计一个类发短信类的方法设计一下短信发送邮件类的设计-设计发邮件类的多文件引用腾讯营销类的设计消息循环消息回掉函数机制lambda按下按钮标签显示文本文本输入框listcombobox选择列表day17  面向对象继承实战昨日小结.,.继承的概念多继承的概念多继承的覆盖私有变量不可以被继承Object类类的常见属性super解决父类重复初始化isinstance多态静态方法类方法数据工具1类的继承数据工具实现小结day18down  正则表达式正则表达式bB贪婪与非贪婪标签.标签解决提取标签名称提取QQ生成邮箱列表工具简单正则表达式编写常见的正则表达式复杂正则表达式编写复杂表达式代码验证作业与小结day18up 正则表达式实践为什么使用正则表达式正则表达式匹配.预编译的概念搜索技能搜索用在找出第一个邮箱手机提取findall字符串切割筛选正则表达式正则表达式替换单个字符判断中括号选择一个字符正则表达式次数正则开头结尾括号与选择正则表达式特殊符号day19down 递归与爬虫实战函数递归模拟文件树文件树事件读取网页抓取邮箱抓取QQ提取http抓取邮箱简单程序框架实现抓取邮箱的框架核心两个函数完成广度遍历深度遍历作.业day19up 深度遍历与广度遍历正则表达式小结最简单递归递归顺序递归求和递归腾讯台阶面试题文件夹的函数遍历文件夹遍历文件夹有层次感栈模拟递归栈模拟递归遍历文件夹栈模拟递归遍历文件夹层次感普及网站提取概念栈修改的逻辑错误广度遍历 day20 正则 爬虫实战行情简介提取页面信息提取股票代码提取股票代码下载功能下载股票批量下载股票抓取1页的股票数据抓取多个页面的股票抓取基金信息作业day21  网络编程与信息安全网络概念UDP协议UDP通信UDP远程控制TCP通信TCP控制测试网站后台网站密码破解作业 day22down  线程通信线程通信线程通信强化线程condition高级线程调度生产者消费者线程池延时线程with作用前台进程后台进程TLS线程独立存储作业day22up  多线程实战多线程核心目标并发主线程与小弟线程多线程解决加速多线程的加速线程冲突基于类实现多线程基于类实现多线程的顺序与乱序互斥锁解决线程冲突死锁-使用锁要注意Rlock解决一个线程反复加锁单线程死锁创建线程的三种风格信号量限制线程数量限定线程数量day23  多进程实战多线程回顾多进程拷贝代码多进程拷贝全局变量获取进程编号subprocess执行LinuxShell信号Linux进程小结__name__进程mutiprocessing创建过程join作用进程不可以用全局变量共享数据进程同步进程pipe发送接收数据进程队列进程队列传输数据进程共享数据进程之间共享数组进程共享字典与list简单小结day23down  多进程多线程综合实战读取CSV写入csv单线程统计行数多线程统计行数多进程统计行数多线程检索数据第一步多线程检索赵琳多线程检索找到通知其他人退出多线程检索开放数据并保存同一个文件作业day24up  多进程多线程综合实战多进程检索第一步多进程检索开房数据求平均市值多线程解决平均市值多进程统计平均市值单线程BFS抓取邮箱多线程并行抓取邮箱多线程采集邮箱并保存csv归并多线程归并文件day25down 文件格式处理doc处理系统处理doc与docx文件处理的小结读取并写入xls与xlsx读取网络pdf中文pdftelnet简介登陆Linux登陆Windows小结day25up  时间编程与单元测试时间的简单风格时间格式化抓取年月日计算时间差时间函数计算时间差date小结python2与3的差异内置函数筛选内置函数map内置的函数sorted新浪数据抓取实时文档测试单元测试函数单元测试一个类day26  综合实战播放音乐设置背景鬼程序协程简介协程传递数据wmv协程编号协程生产者消费者模式office自动化操作wordoffice办公自动化操作exceloffice办公自动化操作outlookoffice办公自动化access开发环境配置访问mdb格式访问accdb格式实现CGI程序显示页面环境变量cgi处理前后端day26down  破解wifi实战扫描wifi扫描网卡区分链接成功或者失败wifi密码爆破工具设计Linux破解简介day27down  飞机大战实战命令行下脱裤创建一个pygame窗体窗体加载背景图片处理消息飞机移动实现基本框架飞机框架飞机销毁双机对战双机对战加载子弹双机与敌机出现发射子弹day27up  数据库实战jython的安装MySQL简介数据库的一般操作MySQL数据类型数据表的创建于删除数据的查询数据的插入数据的删除数据的更新联合查询字段一致数据查询排序排序与分组join与nullmysql用python编程查询数据python数据库增删查改项目简介other  异常与错误综合练习异常与错误的概念作业tryexcept解决异常不出错继续执行密码破解工具进攻数据库爆破mysql异常else异常处理的标准公式弹出异常withas类中使用异常继承自定义异常断言密码工具设计三个独立的类密码工具2类链接3类密码工具1类链接3类密码工具分析作业speech   语音识别问题解决语音识别解决None

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小馒头学python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值