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

目录
  • 一、前情提要
  • 二、并发的概念
  • 三、并发与多线程
  • 四、线程池

一、前情提要

相信来看这篇深造爬虫文章的同学,大部分已经对爬虫有不错的了解了,也在之前已经写过不少爬虫了,但我猜爬取的数据量都较小,因此没有过多的关注爬虫的爬取效率。这里我想问问当我们要爬取的数据量为几十万甚至上百万时,我们会不会需要要等几天才能将数据全都爬取完毕呢?

唯一的办法就是让爬虫可以 7×24 小时不间断工作。因此我们能做的就是多叫几个爬虫一起来爬数据,这样便可大大提升爬虫的效率。

但在介绍Python 如何让多个爬虫一起爬取数据之前,我想先为大家介绍一个概念——并发。

二、并发的概念

为了让大家简单易懂,我就用例子代替复杂的文章来向大家介绍吧

第一个例子
我们用 requests 成功请求一个网页,实际上 requests 做了三件事:
1、根据链接、参数等组合成一个请求;
2、把这个请求发往要爬取的网站,等待网站响应;
3、网站响应后,把结果包装成一个响应对象方便我们使用。

其中步骤 2 花费的时间是最长的,取决于被爬网站的性能,这个时间可能达到几十到几百毫秒。

对这个程序来说:绿色部分代表代码是在 运行 的,黄色部分(步骤 2)代表程序是 空闲 的,因为在等待网站响应。 所以,爬虫代码真正运行的时间很短,大部分时间都浪费在等待网站响应上了。

第二个例子
我们连续用 requests 请求三个网页 A、B、C,执行的过程如下图所示:

同样的,每次步骤 1、3 和 2 所花费时间的差异很大。我们假设步骤 1 和步骤 3 都要花费 1 毫秒,步骤 2 要花费 98 毫秒。那么一个网页要花费 100 毫秒,爬取 A、B、C 三个网页一共花费了 300 毫秒。

这时我们其实遇到一个问题:整个过程的 300 毫秒里,代码运行的时间只有 6 毫秒,剩下有 294 毫秒我们的程序只是空闲在那里等待着网站响应。

第三个例子

想一想,第一个例子里,顺序必须是 1-2-3,因为步骤 2 依赖步骤 1 的结果,步骤 3 依赖步骤 2 的结果。但是第二个例子里,步骤为什么必须是 A1-A2-A3-B1-B2-B3-C1-C2-C3 呢?「爬取网页 B」的步骤 1 其实和「爬取网页 A」的步骤 3 并没有依赖关系。

这张图是什么意思呢?其实就是:在「爬取网页 A」这个过程进行到步骤 2 的时候,程序空闲下来了,这时我们让「爬取网页 B」的步骤 1 开始执行;同样的,「爬取网页 B」的步骤 1 执行完,程序又空闲下来,于是我们安排「爬取网页 C」开始执行。

依然假设步骤 1 和 3 需要花费 1 毫秒,步骤 2 花费 98 毫秒。算一算,只需要102 毫秒!
我们要爬 10 个或者 20 个网页,现在预计分别只需要 109 毫秒和 119 毫秒。而假如我们用第二个例子里的方式运行,则分别需要 1000 毫秒和 2000 毫秒!

可以看到,我们仅仅是利用了爬虫等待网站响应的空闲时间,爬虫的效率就提升了数十倍。当爬取数据量更大时,爬虫效率提升会更加的显著。

回到问题:什么叫并发?

上面第二个例子就不是并发:我要做三件事,然后我一件一件完成它们。

上面的第三个例子就是并发:我们明明要做三件事,但是在这段时间内,我们交错着做这三件事,就好像在 同时做这些事 !

而上面第一个例子里,我们只需要做一件事情,这时不管我们写并发的代码或者普通的代码,它总是步骤 1-2-3 这样被执行完,没有什么区别。

上面第三种例子这种情况,在计算机中被称为并发

让我们用一段代码,来让大家直观的看看并发是什么:

import time
import requests
class Adapter(requests.adapters.HTTPAdapter):
  def send(self, *args, **kwargs):
    global start
    print(
      "步骤 1 结束,耗时",
      round((time.time() - start) * 1000),
      "毫秒"
    )
    return super().send(*args, **kwargs)
s = requests.Session()
s.mount("https://", Adapter())
start = time.time()
r = s.get('https://www.baidu.com')
end = time.time()
print(
  "步骤 2 结束,耗时",
  round(r.elapsed.total_seconds() * 1000),
  "毫秒"
)
print(
  "步骤 3 结束,耗时",
  int((end -start - r.elapsed.total_seconds()) * 1000),
  "毫秒"
)
//输出结果↓
//步骤 1 结束,耗时 2 毫秒
//步骤 2 结束,耗时 66 毫秒
//步骤 3 结束,耗时 1 毫秒

通过以上的讲解,相信大家已经对并发有一个初步的认识了,接下来我们再来讲讲多线程

三、并发与多线程

操作系统为我们提供了两个东西:进程和线程。利用这两样东西,我们可以轻易地实现代码的并发,而不用考虑细枝末节。

例如,我们把下面三个任务丢到三个线程中,操作系统就能让任务A等待时,启动任务B,任务AB等待时,启动任务C,而当任务A等待结束了,接着回去完成任务A,以此类推,在最短的时间内完成所有的任务,而不用挤占时间。

我们来比较一下,有用多线程和没有用多线程的爬虫程序的耗时究竟相差多少!

import time
import requests
# 导入 concurrent.futures 这个包
from concurrent import futures

# 假设我们要爬取 30 个网页
urls = ["https://wpblog.x0y1.com/?p=34"] * 30
session = requests.Session()

# 普通爬虫
start1 = time.time()
results = []
for url in urls:
  r = session.get(url)
  results.append(r.text)

end1 = time.time()
print("普通爬虫耗时", end1-start1, "秒")

# 多线程爬虫
# 初始化一个线程池,最大的同时任务数是 5
executor = futures.ThreadPoolExecutor(max_workers=5)
start2 = time.time()
fs = []
for url in urls:
  # 提交任务到线程池
  f = executor.submit(session.get, url)
  fs.append(f)

# 等待这些任务全部完成
futures.wait(fs)
# 获取任务的结果
result = [f.result().text for f in fs]
end2 = time.time()
print("多线程爬虫耗时", end2-start2, "秒")

#输出结果↓  耗时与线上环境和硬件条件有关
#普通爬虫耗时 3.626128673553467 秒
#多线程爬虫耗时 2.0856518745422363 秒

看到结果对比之后就会知道,通常情况下多线程爬虫的效率会比单线程高很多。而且需要处理的任务量越多的时候,这个差异会越明显。

好,我们再来仔细解读一下这部分多线程爬虫代码,我们取出关键部分看看

# 导入 concurrent.futures 这个包
from concurrent import futures

# 初始化一个线程池,最大的同时任务数是 5
executor = futures.ThreadPoolExecutor(max_workers=5)

concurrent是 Python 自带的库,这个库具有线程池和进程池、管理并行编程任务、处理非确定性的执行流程、进程/线程同步等功能。
executor 就是我们刚刚初始化的线程池,我们调用 executor 的 submit() 方法往里面提交任务。第一个参数 session.get 是提交要运行的函数,第二个参数 url 是提交的函数运行时的参数。

fs = []
for url in urls:
  # 提交任务到线程池
  f = executor.submit(session.get, url)
  fs.append(f)

executor 就是我们刚刚初始化的线程池,我们调用 executor 的 submit() 方法往里面提交任务。第一个参数 session.get 是提交要运行的函数,第二个参数 url 是提交的函数运行时的参数。
executor.submit() 方法会给我们一个返回值,它是一个 future 对象,我们把它赋值给变量 f。

# 等待这些任务全部完成
futures.wait(fs)

fs 是保存了上面所有任务的 future 对象的列表,futures.wait() 方法可以等待直到 fs 里面所有的 future 对象都有结果为止。

# 获取任务的结果
result = [f.result().text for f in fs]

fs 是保存了上面所有任务的 future 对象的列表,我们遍历所有任务的 future 对象,调用 future 对象的 result() 方法,就能得到任务的结果。
那结果是什么类型的呢?取决于提交的任务。比如我们提交的是 session.get(url),它的返回值是一个 response 对象,那我们调用它的 text 属性就能得到响应的完整内容了。

四、线程池

前面我们讲过,线程是操作系统提供给我们的能力,可以把不同的任务放到不同的线程里,这样它们可以同时运行。但是这个能力一定是有限的,并不能无止境的制造线程。如果运行的线程数太多,操作系统在安排这些线程的执行顺序等事情上要花费很大的代价。

我们先来回忆一下一开始的第三个例子,在这个例子里,之所以切换到第二个任务可以提高我们的效率,是因为第一个任务已经处于空闲状态。

但假如我们的线程数非常多,步骤 1 可以一直往图的右下堆叠,直到占满了空闲时间。这时再加线程对爬虫而言是没有意义的,任务同样要排队来运行。

所以线程池其实就是限制了最多同时运行的线程数。比如我们初始化一个最大任务数为 5 的线程池,这样即使我们提交了 100 任务到这个池子里,同时在运行的也只有五个。而一个任务被完成后,也会被移出线程池腾出空间。所以,用线程池可以避免上面提到的两个问题。

其实还有第三个问题,就是考虑到被爬网站的性能和其反爬机制,我们也不应该让机器过快地去运行爬虫。线程池的数量建议可以在 10 左右,电脑性能好而且不担心被爬取网站封禁的可以考虑加到几十,性能差的可以考虑降到 5。

下一篇文章我会介绍一个并发爬取的项目实战,希望有需要的同学来看看!!
文章链接:Python爬虫深造篇(二)——多线程爬取虎扑网页

本次分享到此结束,非常感谢大家阅读!!
有问题欢迎评论区留言!!

更多关于Python多线程爬取网页的资料请关注我们其它相关文章!

(0)

相关推荐

  • Python之多线程爬虫抓取网页图片的示例代码

    目标 嗯,我们知道搜索或浏览网站时会有很多精美.漂亮的图片. 我们下载的时候,得鼠标一个个下载,而且还翻页. 那么,有没有一种方法,可以使用非人工方式自动识别并下载图片.美美哒. 那么请使用python语言,构建一个抓取和下载网页图片的爬虫. 当然为了提高效率,我们同时采用多线程并行方式. 思路分析 Python有很多的第三方库,可以帮助我们实现各种各样的功能.问题在于,我们弄清楚我们需要什么: 1)http请求库,根据网站地址可以获取网页源代码.甚至可以下载图片写入磁盘. 2)解析网页源代码,

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

    最近花些时间学习了一下Python,并写了一个多线程的爬虫程序来获取电影天堂上资源的迅雷下载地址,代码已经上传到GitHub上了,需要的同学可以自行下载.刚开始学习python希望可以获得宝贵的意见. 先来简单介绍一下,网络爬虫的基本实现原理吧.一个爬虫首先要给它一个起点,所以需要精心选取一些URL作为起点,然后我们的爬虫从这些起点出发,抓取并解析所抓取到的页面,将所需要的信息提取出来,同时获得的新的URL插入到队列中作为下一次爬取的起点.这样不断地循环,一直到获得你想得到的所有的信息爬虫的任务

  • 基python实现多线程网页爬虫

    一般来说,使用线程有两种模式, 一种是创建线程要执行的函数, 把这个函数传递进Thread对象里,让它来执行. 另一种是直接从Thread继承,创建一个新的class,把线程执行的代码放到这个新的class里. 实现多线程网页爬虫,采用了多线程和锁机制,实现了广度优先算法的网页爬虫. 先给大家简单介绍下我的实现思路: 对于一个网络爬虫,如果要按广度遍历的方式下载,它是这样的: 1.从给定的入口网址把第一个网页下载下来 2.从第一个网页中提取出所有新的网页地址,放入下载列表中 3.按下载列表中的地

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

    XPath 的安装以及使用 1 . XPath 的介绍 刚学过正则表达式,用的正顺手,现在就把正则表达式替换掉,使用 XPath,有人表示这太坑爹了,早知道刚上来就学习 XPath 多省事 啊.其实我个人认为学习一下正则表达式是大有益处的,之所以换成 XPath ,我个人认为是因为它定位更准确,使用更加便捷.可能有的人对 XPath 和正则表达式的区别不太清楚,举个例子来说吧,用正则表达式提取我们的内容,就好比说一个人想去天安门,地址的描述是左边有一个圆形建筑,右边是一个方形建筑,你去找吧,而使

  • Python实现多线程抓取网页功能实例详解

    本文实例讲述了Python实现多线程抓取网页功能.分享给大家供大家参考,具体如下: 最近,一直在做网络爬虫相关的东西. 看了一下开源C++写的larbin爬虫,仔细阅读了里面的设计思想和一些关键技术的实现. 1.larbin的URL去重用的很高效的bloom filter算法: 2.DNS处理,使用的adns异步的开源组件: 3.对于url队列的处理,则是用部分缓存到内存,部分写入文件的策略. 4.larbin对文件的相关操作做了很多工作 5.在larbin里有连接池,通过创建套接字,向目标站点

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

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

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

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

  • Python进阶之使用selenium爬取淘宝商品信息功能示例

    本文实例讲述了Python进阶之使用selenium爬取淘宝商品信息功能.分享给大家供大家参考,具体如下: # encoding=utf-8 __author__ = 'Jonny' __location__ = '西安' __date__ = '2018-05-14' ''' 需要的基本开发库文件: requests,pymongo,pyquery,selenium 开发流程: 搜索关键字:利用selenium驱动浏览器搜索关键字,得到查询后的商品列表 分析页码并翻页:得到商品页码数,模拟翻页

  • Python 爬取网页图片详解流程

    简介 快乐在满足中求,烦恼多从欲中来 记录程序的点点滴滴. 输入一个网址从这个网址中解析出图片,并将它保存在本地 流程图 程序分析 解析主网址 def get_urls(): url = 'http://www.nipic.com/show/35350678.html' # 主网址 pattern = "(http.*?jpg)" header = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKi

  • 如何利用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 多线程爬取案例

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

  • 浅谈Python爬取网页的编码处理

    背景 中秋的时候,一个朋友给我发了一封邮件,说他在爬链家的时候,发现网页返回的代码都是乱码,让我帮他参谋参谋(中秋加班,真是敬业= =!),其实这个问题我很早就遇到过,之前在爬小说的时候稍微看了一下,不过没当回事,其实这个问题就是对编码的理解不到位导致的. 问题 很普通的一个爬虫代码,代码是这样的: # ecoding=utf-8 import re import requests import sys reload(sys) sys.setdefaultencoding('utf8') url

  • python爬虫爬取网页表格数据

    用python爬取网页表格数据,供大家参考,具体内容如下 from bs4 import BeautifulSoup import requests import csv import bs4 #检查url地址 def check_link(url): try: r = requests.get(url) r.raise_for_status() r.encoding = r.apparent_encoding return r.text except: print('无法链接服务器!!!')

  • Python如何使用BeautifulSoup爬取网页信息

    这篇文章主要介绍了Python如何使用BeautifulSoup爬取网页信息,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 简单爬取网页信息的思路一般是 1.查看网页源码 2.抓取网页信息 3.解析网页内容 4.储存到文件 现在使用BeautifulSoup解析库来爬取刺猬实习Python岗位薪资情况 一.查看网页源码 这部分是我们需要的内容,对应的源码为: 分析源码,可以得知: 1.岗位信息列表在<section class="widg

随机推荐