专栏首页Python进阶之路Python爬虫实战:单线程、多线程和协程性能对比

Python爬虫实战:单线程、多线程和协程性能对比

不自卑也不炫耀,不动声色变好,愿每一步都奔走在自己的热爱中

一、前言

今天我要给大家分享的是如何爬取中农网产品报价数据,并分别用普通的单线程、多线程和协程来爬取,从而对比单线程、多线程和协程在网络爬虫中的性能。

目标URL:https://www.zhongnongwang.com/quote/product-htm-page-1.html

爬取产品品名、最新报价、单位、报价数、报价时间等信息,保存到本地Excel。

二、爬取测试

翻页查看 URL 变化规律:

https://www.zhongnongwang.com/quote/product-htm-page-1.html
https://www.zhongnongwang.com/quote/product-htm-page-2.html
https://www.zhongnongwang.com/quote/product-htm-page-3.html
https://www.zhongnongwang.com/quote/product-htm-page-4.html
https://www.zhongnongwang.com/quote/product-htm-page-5.html
https://www.zhongnongwang.com/quote/product-htm-page-6.html

检查网页,可以发现网页结构简单,容易解析和提取数据。

思路:每一条产品报价信息在 class 为 tb 的 table 标签下的 tbody 下的 tr 标签里,获取到所有 tr 标签的内容,然后遍历,从中提取出每一个产品品名、最新报价、单位、报价数、报价时间等信息。

# -*- coding: UTF-8 -*-
"""
@File    :demo.py
@Author  :叶庭云
@CSDN    :https://yetingyun.blog.csdn.net/
"""
import requests
import logging
from fake_useragent import UserAgent
from lxml import etree


# 日志输出的基本配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 随机产生请求头
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
url = 'https://www.zhongnongwang.com/quote/product-htm-page-1.html'
# 伪装请求头
headers = {
    "Accept-Encoding": "gzip",  # 使用gzip压缩传输数据让访问更快
    "User-Agent": ua.random
}
# 发送请求  获取响应
rep = requests.get(url, headers=headers)
print(rep.status_code)    # 200
# Xpath定位提取数据
html = etree.HTML(rep.text)
items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')
logging.info(f'该页有多少条信息:{len(items)}')  # 一页有20条信息
# 遍历提取出数据
for item in items:
    name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名
    price = ''.join(item.xpath('.//td[3]/text()'))   # 最新报价
    unit = ''.join(item.xpath('.//td[4]/text()'))    # 单位
    nums = ''.join(item.xpath('.//td[5]/text()'))    # 报价数
    time_ = ''.join(item.xpath('.//td[6]/text()'))   # 报价时间
    logging.info([name, price, unit, nums, time_])

运行结果如下:

可以成功爬取到数据,接下来分别用普通的单线程、多线程和协程来爬取 50 页的数据、保存到Excel。

三、单线程爬虫

# -*- coding: UTF-8 -*-
"""
@File    :单线程.py
@Author  :叶庭云
@CSDN    :https://yetingyun.blog.csdn.net/
"""
import requests
import logging
from fake_useragent import UserAgent
from lxml import etree
import openpyxl
from datetime import datetime

# 日志输出的基本配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 随机产生请求头
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
wb = openpyxl.Workbook()
sheet = wb.active
sheet.append(['品名', '最新报价', '单位', '报价数', '报价时间'])
start = datetime.now()

for page in range(1, 51):
    # 构造URL
    url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'
    # 伪装请求头
    headers = {
        "Accept-Encoding": "gzip",  # 使用gzip压缩传输数据让访问更快
        "User-Agent": ua.random
    }
    # 发送请求  获取响应
    rep = requests.get(url, headers=headers)
    # print(rep.status_code)
    # Xpath定位提取数据
    html = etree.HTML(rep.text)
    items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')
    logging.info(f'该页有多少条信息:{len(items)}')  # 一页有20条信息
    # 遍历提取出数据
    for item in items:
        name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名
        price = ''.join(item.xpath('.//td[3]/text()'))   # 最新报价
        unit = ''.join(item.xpath('.//td[4]/text()'))    # 单位
        nums = ''.join(item.xpath('.//td[5]/text()'))    # 报价数
        time_ = ''.join(item.xpath('.//td[6]/text()'))   # 报价时间
        sheet.append([name, price, unit, nums, time_])
        logging.info([name, price, unit, nums, time_])


wb.save(filename='data1.xlsx')
delta = (datetime.now() - start).total_seconds()
logging.info(f'用时:{delta}s')

运行结果如下:

单线程爬虫必须上一个页面爬取完成才能继续爬取,还可能受当时网络状态影响,用时48.528703s,才将数据爬取完,速度比较慢。

四、多线程爬虫

# -*- coding: UTF-8 -*-
"""
@File    :多线程.py
@Author  :叶庭云
@CSDN    :https://yetingyun.blog.csdn.net/
"""
import requests
import logging
from fake_useragent import UserAgent
from lxml import etree
import openpyxl
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED
from datetime import datetime

# 日志输出的基本配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 随机产生请求头
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
wb = openpyxl.Workbook()
sheet = wb.active
sheet.append(['品名', '最新报价', '单位', '报价数', '报价时间'])
start = datetime.now()


def get_data(page):
    # 构造URL
    url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'
    # 伪装请求头
    headers = {
        "Accept-Encoding": "gzip",    # 使用gzip压缩传输数据让访问更快
        "User-Agent": ua.random
    }
    # 发送请求  获取响应
    rep = requests.get(url, headers=headers)
    # print(rep.status_code)
    # Xpath定位提取数据
    html = etree.HTML(rep.text)
    items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')
    logging.info(f'该页有多少条信息:{len(items)}')  # 一页有20条信息
    # 遍历提取出数据
    for item in items:
        name = ''.join(item.xpath('.//td[1]/a/text()'))   # 品名
        price = ''.join(item.xpath('.//td[3]/text()'))    # 最新报价
        unit = ''.join(item.xpath('.//td[4]/text()'))     # 单位
        nums = ''.join(item.xpath('.//td[5]/text()'))     # 报价数
        time_ = ''.join(item.xpath('.//td[6]/text()'))    # 报价时间
        sheet.append([name, price, unit, nums, time_])
        logging.info([name, price, unit, nums, time_])


def run():
    # 爬取1-50页
    with ThreadPoolExecutor(max_workers=6) as executor:
        future_tasks = [executor.submit(get_data, i) for i in range(1, 51)]
        wait(future_tasks, return_when=ALL_COMPLETED)

    wb.save(filename='data2.xlsx')
    delta = (datetime.now() - start).total_seconds()
    print(f'用时:{delta}s')


run()

运行结果如下:

多线程爬虫爬取效率提升非常可观,用时 2.648128s,爬取速度很快。

五、异步协程爬虫

# -*- coding: UTF-8 -*-
"""
@File    :demo1.py
@Author  :叶庭云
@CSDN    :https://yetingyun.blog.csdn.net/
"""
import aiohttp
import asyncio
import logging
from fake_useragent import UserAgent
from lxml import etree
import openpyxl
from datetime import datetime

# 日志输出的基本配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 随机产生请求头
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
wb = openpyxl.Workbook()
sheet = wb.active
sheet.append(['品名', '最新报价', '单位', '报价数', '报价时间'])
start = datetime.now()


class Spider(object):
    def __init__(self):
        # self.semaphore = asyncio.Semaphore(6)  # 信号量,有时候需要控制协程数,防止爬的过快被反爬
        self.header = {
                "Accept-Encoding": "gzip",    # 使用gzip压缩传输数据让访问更快
                "User-Agent": ua.random
            }

    async def scrape(self, url):
        # async with self.semaphore:  # 设置最大信号量,有时候需要控制协程数,防止爬的过快被反爬
        session = aiohttp.ClientSession(headers=self.header, connector=aiohttp.TCPConnector(ssl=False))
        response = await session.get(url)
        result = await response.text()
        await session.close()
        return result

    async def scrape_index(self, page):
        url = f'https://www.zhongnongwang.com/quote/product-htm-page-{page}.html'
        text = await self.scrape(url)
        await self.parse(text)

    async def parse(self, text):
        # Xpath定位提取数据
        html = etree.HTML(text)
        items = html.xpath('/html/body/div[10]/table/tr[@align="center"]')
        logging.info(f'该页有多少条信息:{len(items)}')  # 一页有20条信息
        # 遍历提取出数据
        for item in items:
            name = ''.join(item.xpath('.//td[1]/a/text()'))  # 品名
            price = ''.join(item.xpath('.//td[3]/text()'))  # 最新报价
            unit = ''.join(item.xpath('.//td[4]/text()'))  # 单位
            nums = ''.join(item.xpath('.//td[5]/text()'))  # 报价数
            time_ = ''.join(item.xpath('.//td[6]/text()'))  # 报价时间
            sheet.append([name, price, unit, nums, time_])
            logging.info([name, price, unit, nums, time_])

    def main(self):
        # 50页的数据
        scrape_index_tasks = [asyncio.ensure_future(self.scrape_index(page)) for page in range(1, 51)]
        loop = asyncio.get_event_loop()
        tasks = asyncio.gather(*scrape_index_tasks)
        loop.run_until_complete(tasks)


if __name__ == '__main__':
    spider = Spider()
    spider.main()
    wb.save('data3.xlsx')
    delta = (datetime.now() - start).total_seconds()
    print("用时:{:.3f}s".format(delta))

运行结果如下:

而到了协程异步爬虫,爬取速度更快,嗖的一下,用时 0.930s 就爬取完 50 页数据,aiohttp + asyncio 异步爬虫竟恐怖如斯。异步爬虫在服务器能承受高并发的前提下增加并发数量,爬取效率提升是非常可观的,比多线程还要快一些。

三种爬虫都将 50 页的数据爬取下来保存到了本地,结果如下:

六、总结回顾

今天我演示了简单的单线程爬虫、多线程爬虫和协程异步爬虫。可以看到一般情况下异步爬虫速度最快,多线程爬虫略慢一点,单线程爬虫速度较慢,必须上一个页面爬取完成才能继续爬取。

但协程异步爬虫相对来说并不是那么好编写,数据抓取无法使用 request 库,只能使用aiohttp,而且爬取数据量大时,异步爬虫需要设置最大信号量来控制协程数,防止爬的过快被反爬。所以在实际编写 Python 爬虫时,我们一般都会使用多线程爬虫来提速,但必须注意的是网站都有 ip 访问频率限制,爬的过快可能会被封ip,所以一般我们在多线程提速的同时可以使用代理 ip 来并发地爬取数据。

  • 多线程(multithreading):是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作 “线程” (Thread),利用它编程的概念就叫作 “多线程处理”。
  • 异步(asynchronous):为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式,不相关的程序单元之间可以是异步的。例如,爬虫下载网页。调度程序调用下载程序后,即可调度其他任务,而无需与该下载任务保持通信以协调行为。不同网页的下载、保存等操作都是无关的,也无需相互通知协调。这些异步操作的完成时刻并不确定。简言之,异步意味着无序
  • 协程(coroutine),又称微线程、纤程,协程是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重入时,就相当于进入上一次调用的状态。协程本质上是个单进程,协程相对于多进程来说,无需线程上下文切换的开销,无需原子操作锁定及同步的开销,编程模型也非常简单。我们可以使用协程来实现异步操作,比如在网络爬虫场景下,我们发出一个请求之后,需要等待一定的时间才能得到响应,但其实在这个等待过程中,程序可以干许多其他的事情,等到响应得到之后才切换回来继续处理,这样可以充分利用 CPU 和其他资源,这就是协程的优势

作者:叶庭云 CSDN:https://yetingyun.blog.csdn.net/ 热爱可抵岁月漫长,发现求知的乐趣,在不断总结和学习中进步,与诸君共勉。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Python爬虫:单线程、多线程和协程的爬虫性能对比

    今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程、多线程和协程来爬取,从而对比单线程、多线程和协程在网络爬虫中的性能。

    快学Python
  • Python 多线程爬虫实战

    在线程中,访问一些全局变量,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,那么Python内置了一个线程安全的模块叫做queue模...

    Python知识大全
  • python爬虫入门实战(二)!多线程爬虫!

    在 python爬虫入门实战!爬取博客文章标题和链接! 上一篇文章我们已经学会基本用法了。最近我又学到一新技能,让它爬的更快一些。

    白玉无冰
  • python实现多线程爬虫

    ubuntu16.04,python3.6,bs4,virtualenv(虚拟环境)

    会呼吸的Coder
  • 爬虫进阶Python多线程和多进程

    Python多线程,thread标准库。都说Python的多线程是鸡肋,推荐使用多进程。

    andrew_a
  • 1个月轻松掌握Python 五大核心知识点

    Python是一种计算机程序设计语言。你可能已经听说过很多种流行的编程语言,比如非常难学的C语言,非常流行的Java语言,适合初学者的Basic语言,适合网页编...

    python学习教程
  • 零基础Python学习路线及阶段学习目标

      1、Python编程基础,语法规则,函数与参数,数据类型,模块与包,文件IO,培养扎实的Python编程基本功,同时对Python核心对象和库的编程有熟练的...

    python学习教程
  • Python 进程、线程和协程实战指南

    前些日子写过几篇关于线程和进程的文章,概要介绍了Python内置的线程模块(threading)和进程模块(multiprocessing)的使用方法,侧重点是...

    Python编程与实战
  • 一幅图讲清楚Python在大数据与人工智能时代的地位

    大数据与人工智能时代,掌握Python基础后,我们可以选择数据分析方向、人工智能方向、全栈开发方向... ? 如果想要追赶 Python 的热潮,应该如何学习呢...

    昱良
  • 零基础如何系统地自学Python编程?

    零基础如何系统地自学Python编程?绝大多数零基础转行者学习编程的目的就是想找一份高薪有发展前景的工作,哪个编程语言就业前景好越值得学习。零基础的同学学Pyt...

    python学习教程
  • Python网络爬虫工程师需要掌握的核心技术

    为了让具备Python基础的人群适合岗位的需求,小编推出了一门全面的、系统的、简易的Python网络爬虫入门级课程,不仅讲解了学习网络爬虫必备的基础知识,而且加...

    python学习教程
  • 怒肝半月!Python 学习路线+资源大汇总

    视频地址:https://www.bilibili.com/video/BV133411C7u5/

    程序员鱼皮
  • Python爬虫小白入门必读,成为大牛必须经历的三个阶段

    学习任何一门技术,都应该带着目标去学习,目标就像一座灯塔,指引你前进,很多人学着学着就学放弃了,很大部分原因是没有明确目标,所以,一定要明确学习目的,在你准备学...

    一墨编程学习
  • Python爬虫学习路线

    1.刚上手的时候肯定是先过一遍Python最基本的知识,比如说:变量、数据结构、语法等,基础过的很快,基本上1~2周时间就能过完了,我当时是在这儿看的基础:Py...

    py3study
  • Python多线程多进程实例对比解析

    可以看到在耗cpu的应用中,多进程明显优于多线程 2.6130592823028564 < 3.905290126800537

    砸漏
  • 关于Python爬虫,这里有一条高效的学习路径

    关键字全网搜索最新排名 【机器学习算法】:排名第一 【机器学习】:排名第一 【Python】:排名第三 【算法】:排名第四 ? 如果你仔细观察,就不难发现,懂爬...

    昱良
  • Python爬虫 | 一条高效的学习路径

    数据是创造和决策的原材料,高质量的数据都价值不菲。而利用爬虫,我们可以获取大量的价值数据,经分析可以发挥巨大的价值,比如:

    conanma
  • NB,用这一篇文章带你了解什么是爬虫?

    小詹说:对于学 Python 的小伙伴来说,爬虫是大多数人的入门菜,很是因吹斯汀。那么到底什么是爬虫呢,这篇文章用一个简单的语言来一节入门课。以下为原文。

    小小詹同学
  • 爬虫还在用Python?我与Node.js不得不说的故事

    大数据文摘

扫码关注云+社区

领取腾讯云代金券