Python 多线程爬取案例

目录
  • 前言
  • 一、多进程库(multiprocessing)
  • 二、多线程爬虫
  • 三、案例实操
  • 四、案例解析
    • 1、获取网页内容
    • 2、获取每一章链接
    • 3、获取每一章的正文并返回章节名和正文
    • 4、将每一章保存到本地
    • 5、多线程爬取文章

前言

简单的爬虫只有一个进程、一个线程,因此称为​​单线程爬虫​​。单线程爬虫每次只访问一个页面,不能充分利用计算机的网络带宽。一个页面最多也就几百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 多线程爬取内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python进阶多线程爬取网页项目实战

    目录 一.网页分析 二.代码实现 一.网页分析 这次我们选择爬取的网站是水木社区的Python页面 网页:https://www.mysmth.net/nForum/#!board/Python?p=1 根据惯例,我们第一步还是分析一下页面结构和翻页时的请求. 通过前三页的链接分析后得知,每一页链接中最后的参数是页数,我们修改它即可得到其他页面的数据. 再来分析一下,我们需要获取帖子的链接就在id 为 body 的 section下,然后一层一层找到里面的 table,我们就能遍历这些链接的标题

  • Python进阶篇之多线程爬取网页

    目录 一.前情提要 二.并发的概念 三.并发与多线程 四.线程池 一.前情提要 相信来看这篇深造爬虫文章的同学,大部分已经对爬虫有不错的了解了,也在之前已经写过不少爬虫了,但我猜爬取的数据量都较小,因此没有过多的关注爬虫的爬取效率.这里我想问问当我们要爬取的数据量为几十万甚至上百万时,我们会不会需要要等几天才能将数据全都爬取完毕呢? 唯一的办法就是让爬虫可以 7×24 小时不间断工作.因此我们能做的就是多叫几个爬虫一起来爬数据,这样便可大大提升爬虫的效率. 但在介绍Python 如何让多个爬虫一

  • python结合多线程爬取英雄联盟皮肤(原理分析)

    1.什么是多线程? 多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率.线程是在同一时间需要完成多项任务的时候实现的. 为什么要使用多线程 线程在程序中是独立的.并发的执行流.与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存.文件句柄和其他进程应有的状态. 因为线程的划分尺度小于进程,使得多线程程序的并发性高.进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率. 线程比进程具有更高的性能,这是由于同一个进程

  • Python使用requests xpath 并开启多线程爬取西刺代理ip实例

    我就废话不多说啦,大家还是直接看代码吧! import requests,random from lxml import etree import threading import time angents = [ "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "Mozilla/4.0 (compati

  • 如何利用python多线程爬取天气网站图片并保存

    目录 1.1 题目 1.2 思路 1.2.1 发送请求 1.2.2 解析网页 1.2.3 获取结点 1.2.4 数据保存 (单线程) 1.2.4 数据保存 (多线程) 总结 1.1 题目 指定一个网站,爬取这个网站中的所有的所有图片,例如中国气象网(www.weather.com.cn),分别使用单线程和多线程的方式爬取.(限定爬取图片数量为学号后3位) 输出信息: 将下载的Url信息在控制台输出,并将下载的图片存储在images子文件中,并给出截图. 1.2 思路 1.2.1 发送请求 构造请

  • python多线程爬取西刺代理的示例代码

    西刺代理是一个国内IP代理,由于代理倒闭了,所以我就把原来的代码放出来供大家学习吧. 镜像地址:https://www.blib.cn/url/xcdl.html 首先找到所有的tr标签,与class="odd"的标签,然后提取出来. 然后再依次找到tr标签里面的所有td标签,然后只提取出里面的[1,2,5,9]这四个标签的位置,其他的不提取. 最后可以写出提取单一页面的代码,提取后将其保存到文件中. import sys,re,threading import requests,lx

  • Python爬虫利用多线程爬取 LOL 高清壁纸

    目录 页面分析 抓取思路 数据采集 程序运行 总结 前言: 随着移动端的普及出现了很多的移动 APP,应用软件也随之流行起来. 最近又捡起来了英雄联盟手游,感觉还行,PC 端英雄联盟可谓是爆火的游戏,不知道移动端的英雄联盟前途如何,那今天我们使用到多线程的方式爬取 LOL 官网英雄高清壁纸. 页面分析 目标网站:英雄联盟 官网界面如图所示,显而易见,一个小图表示一个英雄,我们的目的是爬取每一个英雄的所有皮肤图片,全部下载下来并保存到本地. 次级页面 上面的页面我们称为主页面,次级页面也就是每一个

  • python 多线程爬取壁纸网站的示例

    基本开发环境 · Python 3.6 · Pycharm 需要导入的库 目标网页分析 网站是静态网站,没有加密,可以直接爬取 整体思路: 1.先在列表页面获取每张壁纸的详情页地址 2.在壁纸详情页面获取壁纸真实高清url地址 3.保存地址 代码实现 模拟浏览器请请求网页,获取网页数据 这里只选择爬取前10页的数据 代码如下 import threading import parsel import requests def get_html(html_url): ''' 获取网页源代码 :pa

  • Python 多线程爬取案例

    目录 前言 一.多进程库(multiprocessing) 二.多线程爬虫 三.案例实操 四.案例解析 1.获取网页内容 2.获取每一章链接 3.获取每一章的正文并返回章节名和正文 4.将每一章保存到本地 5.多线程爬取文章 前言 简单的爬虫只有一个进程.一个线程,因此称为​​单线程爬虫​​.单线程爬虫每次只访问一个页面,不能充分利用计算机的网络带宽.一个页面最多也就几百KB,所以爬虫在爬取一个页面的时候,多出来的网速和从发起请求到得到源代码中间的时间都被浪费了.如果可以让爬虫同时访问10个页面

  • Python多线程爬取豆瓣影评API接口

    爬虫库 使用简单的requests库,这是一个阻塞的库,速度比较慢. 解析使用XPATH表达式 总体采用类的形式 多线程 使用concurrent.future并发模块,建立线程池,把future对象扔进去执行即可实现并发爬取效果 数据存储 使用Python ORM sqlalchemy保存到数据库,也可以使用自带的csv模块存在CSV中. API接口 因为API接口存在数据保护情况,一个电影的每一个分类只能抓取前25页,全部评论.好评.中评.差评所有分类能爬100页,每页有20个数据,即最多为

  • python爬虫爬取快手视频多线程下载功能

    环境: python 2.7 + win10 工具:fiddler postman 安卓模拟器 首先,打开fiddler,fiddler作为http/https 抓包神器,这里就不多介绍. 配置允许https 配置允许远程连接 也就是打开http代理 电脑ip: 192.168.1.110 然后 确保手机和电脑是在一个局域网下,可以通信.由于我这边没有安卓手机,就用了安卓模拟器代替,效果一样的. 打开手机浏览器,输入192.168.1.110:8888   也就是设置的代理地址,安装证书之后才能

随机推荐