我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Python 多线程爬取案例

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Python 多线程爬取案例

前言

简单的爬虫只有一个进程、一个线程,因此称为​​单线程爬虫​​。单线程爬虫每次只访问一个页面,不能充分利用计算机的网络带宽。一个页面最多也就几百KB,所以爬虫在爬取一个页面的时候,多出来的网速和从发起请求到得到源代码中间的时间都被浪费了。如果可以让爬虫同时访问10个页面,就相当于爬取速度提高了10倍。为了达到这个目的,就需要使用​​多线程技术​​了。

微观上的单线程,在宏观上就像同时在做几件事。这种机制在 ​​I/O(Input/Output,输入/输出)密集型的操作​​上影响不大,但是在​​CPU计算密集型的操作​​上面,由于只能使用CPU的一个核,就会对性能产生非常大的影响。所以涉及计算密集型的程序,就需要使用多进程。

爬虫属于I/O密集型的程序,所以使用多线程可以大大提高爬取效率。

一、多进程库(multiprocessing)

​multiprocessing​​ 本身是​​Python的多进程库​​,用来处理与多进程相关的操作。但是由于进程与进程之间不能直接共享内存和堆栈资源,而且启动新的进程开销也比线程大得多,因此使用多线程来爬取比使用多进程有更多的优势。

multiprocessing下面有一个​​dummy模块​​ ,它可以让Python的线程使用multiprocessing的各种方法。

dummy下面有一个​​Pool类​​ ,它用来实现线程池。这个线程池有一个​​map()方法​​,可以让线程池里面的所有线程都“同时”执行一个函数

测试案例     计算0~9的每个数的平方

# 循环
for i in range(10):
print(i ** i)

也许你的第一反应会是上面这串代码,循环不就行了吗?反正就10个数!

这种写法当然可以得到结果,但是代码是一个数一个数地计算,效率并不高。而如果使用多线程的技术,让代码同时计算很多个数的平方,就需要使用 ​​multiprocessing.dummy​​ 来实现:

from multiprocessing.dummy import Pool

# 平方函数
def calc_power2(num):
return num * num

# 定义三个线程池
pool = Pool(3)
# 定义循环数
origin_num = [x for x in range(10)]
# 利用map让线程池中的所有线程‘同时'执行calc_power2函数
result = pool.map(calc_power2, origin_num)
print(f'计算1-10的平方分别为:{result}')

在上面的代码中,先定义了一个函数用来计算平方,然后初始化了一个有3个线程的线程池。这3个线程负责计算10个数字的平方,谁先计算完手上的这个数,谁就先取下一个数继续计算,直到把所有的数字都计算完成为止。

在这个例子中,线程池的 ​​map()​​ 方法接收两个参数,第1个参数是函数名,第2个参数是一个列表。注意:第1个参数仅仅是函数的名字,是不能带括号的。第2个参数是一个可迭代的对象,这个可迭代对象里面的每一个元素都会被函数 ​​clac_power2()​​ 接收来作为参数。除了列表以外,元组、集合或者字典都可以作为 ​​map()​​ 的第2个参数。

二、多线程爬虫

由于爬虫是 ​​I/O密集型​​ 的操作,特别是在请求网页源代码的时候,如果使用单线程来开发,会浪费大量的时间来等待网页返回,所以把多线程技术应用到爬虫中,可以大大提高爬虫的运行效率。

下面通过两段代码来对比单线程爬虫和多线程爬虫爬取​​CSDN首页​​的性能差异:

import time
import requests
from multiprocessing.dummy import Pool

# 自定义函数
def query(url):
requests.get(url)

start = time.time()
for i in range(100):
query('https://www.csdn.net/')
end = time.time()
print(f'单线程循环访问100次CSDN,耗时:{end - start}')

start = time.time()
url_list = []
for i in range(100):
url_list.append('https://www.csdn.net/')
pool = Pool(5)
pool.map(query, url_list)
end = time.time()
print(f'5线程访问100次CSDN,耗时:{end - start}')

从运行结果可以看到,一个线程用时约​​69.4s​​,5个线程用时约​​14.3s​​,时间是单线程的​​五分之一​​左右。从时间上也可以看到5个线程“同时运行”的效果。

但并不是说线程池设置得越大越好。从上面的结果也可以看到,5个线程运行的时间其实比一个线程运行时间的五分之一(​​13.88s​​)要多一点。这多出来的一点其实就是线程切换的时间。这也从侧面反映了Python的多线程在微观上还是串行的。

因此,如果线程池设置得过大,线程切换导致的开销可能会抵消多线程带来的性能提升。线程池的大小需要根据实际情况来确定,并没有确切的数据。

三、案例实操

从 ​ ​https://www.kanunu8.com/book2/11138/​​ 爬取​​《北欧众神》​​所有章节的网址,再通过一个多线程爬虫将每一章的内容爬取下来。在本地创建一个“北欧众神”文件夹,并将小说中的每一章分别保存到这个文件夹中,且每一章保存为一个文件。

import re
import os
import requests
from multiprocessing.dummy import Pool

# 爬取的主网站地址
start_url = 'https://www.kanunu8.com/book2/11138/'
"""
获取网页源代码
:param url: 网址
:return: 网页源代码
"""
def get_source(url):
html = requests.get(url)
return html.content.decode('gbk') # 这个网页需要使用gbk方式解码才能让中文正常显示

"""
获取每一章链接,储存到一个列表中并返回
:param html: 目录页源代码
:return: 每章链接
"""
def get_article_url(html):
article_url_list = []
article_block = re.findall('正文(.*?)<div class="clear">', html, re.S)[0]
article_url = re.findall('<a href="(\d*.html)" rel="external nofollow"  rel="external nofollow" >', article_block, re.S)
for url in article_url:
article_url_list.append(start_url + url)
return article_url_list

"""
获取每一章的正文并返回章节名和正文
:param html: 正文源代码
:return: 章节名,正文
"""
def get_article(html):
chapter_name = re.findall('<h1>(.*?)<br>', html, re.S)[0]
text_block = re.search('<p>(.*?)</p>', html, re.S).group(1)
text_block = text_block.replace(' ', '') # 替换   网页空格符
text_block = text_block.replace('<p>', '') # 替换 <p></p> 中的嵌入的 <p></p> 中的 <p>
return chapter_name, text_block

"""
将每一章保存到本地
:param chapter: 章节名, 第X章
:param article: 正文内容
:return: None
"""
def save(chapter, article):
os.makedirs('北欧众神', exist_ok=True) # 如果没有"北欧众神"文件夹,就创建一个,如果有,则什么都不做"
with open(os.path.join('北欧众神', chapter + '.txt'), 'w', encoding='utf-8') as f:
f.write(article)

"""
根据正文网址获取正文源代码,并调用get_article函数获得正文内容最后保存到本地
:param url: 正文网址
:return: None
"""
def query_article(url):
article_html = get_source(url)
chapter_name, article_text = get_article(article_html)
# print(chapter_name)
# print(article_text)
save(chapter_name, article_text)

if __name__ == '__main__':
toc_html = get_source(start_url)
toc_list = get_article_url(toc_html)
pool = Pool(4)
pool.map(query_article, toc_list)

四、案例解析

1、获取网页内容

# 爬取的主网站地址
start_url = 'https://www.kanunu8.com/book2/11138/'
def get_source(url):
html = requests.get(url)
return html.content.decode('gbk') # 这个网页需要使用gbk方式解码才能让中文正常显示

这一部分并不难,主要就是指明需要爬取的网站,并通过 ​​request.get()​​ 的请求方式获取网站,在通过 ​​content.decode()​​ 获取网页的解码内容,其实就是获取网页的源代码。

2、获取每一章链接

def get_article_url(html):
article_url_list = []
# 根据正文锁定每一章节的链接区域
article_block = re.findall('正文(.*?)<div class="clear">', html, re.S)[0]
# 获取到每一章的链接
article_url = re.findall('<a href="(\d*.html)" rel="external nofollow"  rel="external nofollow" >', article_block, re.S)
for url in article_url:
article_url_list.append(start_url + url)
return

这里需要获取到每一章的链接,首先我们根据正文锁定每一章节的链接区域,然后在链接区域中获取到每一章的链接,形成列表返回。

在获取每章链接的时候,通过页面源码可以发现均为​​数字开头​​,​​.html结尾​​,于是利用正则 ​​(\d*.html)​​ 匹配即可:

3、获取每一章的正文并返回章节名和正文

def get_article(html):
chapter_name = re.findall('<h1>(.*?)<br>', html, re.S)[0]
text_block = re.search('<p>(.*?)</p>', html, re.S).group(1)
text_block = text_block.replace(' ', '') # 替换   网页空格符
text_block = text_block.replace('<p>', '') # 替换 <p></p> 中的嵌入的 <p></p> 中的 <p>
return chapter_name,

这里利用正则分别匹配出每章的标题和正文内容:

格式化后:

4、将每一章保存到本地

"""
将每一章保存到本地
:param chapter: 章节名, 第X章
:param article: 正文内容
:return: None
"""
def save(chapter, article):
os.makedirs('北欧众神', exist_ok=True) # 如果没有"北欧众神"文件夹,就创建一个,如果有,则什么都不做"
with open(os.path.join('北欧众神', chapter + '.txt'), 'w', encoding='utf-8') as f:
f.write(article)

这里获取到我们处理好的文章标题及内容,并将其写入本地磁盘。首先创建文件夹,然后打开文件夹以 ​​章节名​​+​​.txt​​ 结尾存储每章内容。

5、多线程爬取文章

"""
根据正文网址获取正文源代码,并调用get_article函数获得正文内容最后保存到本地
:param url: 正文网址
:return: None
"""
def query_article(url):
article_html = get_source(url)
chapter_name, article_text = get_article(article_html)
# print(chapter_name)
# print(article_text)
save(chapter_name, article_text)

if __name__ == '__main__':
toc_html = get_source(start_url)
toc_list = get_article_url(toc_html)
pool = Pool(4)
pool.map(query_article, toc_list)

这里 ​​query_article​​ 调用 ​​get_source​​、​​get_article​​ 函数获取以上分析的内容,再调用 ​​save​​ 函数进行本地存储,主入口main中创建线程池,包含4个线程。

​map()方法​​,可以让线程池里面的所有线程都“同时”执行一个函数。 ​​同时map()​​ 方法接收两个参数,第1个参数是函数名,第2个参数是一个列表。这里我们需要对每一个章节进行爬取,所以应该是遍历​​章节链接的列表​​(调用 ​​get_article_url​​ 获取),执行 ​​query_article​​ 方法进行爬取保存。

最后运行程序即可!

到此这篇关于Python 多线程爬取案例的文章就介绍到这了,更多相关Python 多线程爬取内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Python 多线程爬取案例

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python多线程爬虫简单示例

python是支持多线程的,主要是通过thread和threading这两个模块来实现的。thread模块是比较底层的模块,threading模块是对thread做了一些包装的,可以更加方便的使用。 虽然python的多线程受GIL限制,并
2022-06-04

使用Python多线程爬虫爬取电影天堂资源

最近花些时间学习了一下Python,并写了一个多线程的爬虫程序来获取电影天堂上资源的迅雷下载地址,代码已经上传到GitHub上了,需要的同学可以自行下载。刚开始学习python希望可以获得宝贵的意见。先来简单介绍一下,网络爬虫的基本实现原理
2022-06-04

多线程案例(4)-线程池

文章目录 多线程案例四四、线程池 大家好,我是晓星航。今天为大家带来的是 多线程案例-线程池 相关的讲解!😀 多线程案例四 四、线程池 线程池是什么 虽然创建线程 / 销毁线程 的开销 想象这么一个场景:
2023-08-19

python爬虫中多线程和多进程的示例分析

小编给大家分享一下python爬虫中多线程和多进程的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象的脚本语言,其最初的设计是用于
2023-06-14

Python+多线程+队列爬虫

Python+多线程+队列,爬虫例子# -*- coding: utf-8-*-import urllib2import urllibimport jsonimport timeimport datetimeimport threading
2023-01-31

Python 爬虫多线程详解及实例代码

python是支持多线程的,主要是通过thread和threading这两个模块来实现的。thread模块是比较底层的模块,threading模块是对thread做了一些包装的,可以更加方便的使用。虽然python的多线程受GIL限制,并不
2022-06-04

python爬虫入门八:多进程/多线程

引用虫师的解释:计算机程序只不过是磁盘中可执行的,二进制(或其它类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期。进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈以及其它记
2023-01-30

Python怎么利用多线程爬取LOL高清壁纸

这篇“Python怎么利用多线程爬取LOL高清壁纸”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python怎么利用多线程爬
2023-07-02

java多线程爬虫爬取百度图片的方法

小编给大家分享一下java多线程爬虫爬取百度图片的方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Java的特点有哪些Java的特点有哪些1.Java语言作为静
2023-06-14

Python 爬虫学习笔记之多线程爬虫

XPath 的安装以及使用 1 . XPath 的介绍 刚学过正则表达式,用的正顺手,现在就把正则表达式替换掉,使用 XPath,有人表示这太坑爹了,早知道刚上来就学习 XPath 多省事 啊。其实我个人认为学习一下正则表达式是大有益处的,
2022-06-04

怎么在python中利用多线程爬取网站壁纸

本篇文章给大家分享的是有关怎么在python中利用多线程爬取网站壁纸,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Python主要用来做什么Python主要应用于:1、Web开
2023-06-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录