Python的多线程爬虫详解

2023-05-16

多线程使用流程

Python 提供了两个支持多线程的模块,分别是 _thread 和 threading。其中 _thread 模块偏底层,它相比于 threading 模块功能有限,因此推荐大家使用 threading 模块。 threading 中不仅包含了  _thread 模块中的所有方法,还提供了一些其他方法,如下所示:

  • threading.currentThread() 返回当前的线程变量。
  • threading.enumerate() 返回一个所有正在运行的线程的列表。
  • threading.activeCount() 返回正在运行的线程数量。

线程的具体使用方法如下所示:

  1. from threading import Thread
  2. ​#线程创建、启动、回收
  3. t = Thread(target=函数名) # 创建线程对象
  4. t.start() # 创建并启动线程
  5. t.join() # 阻塞等待回收线程

创建多线程的具体流程:

  1. t_list = []
  2. for i in range(5):
  3. t = Thread(target=函数名)
  4. t_list.append(t)
  5. t.start()
  6. for t in t_list:
  7. t.join()

除了使用该模块外,您也可以使用  Thread  线程类来创建多线程。

在处理线程的过程中要时刻注意线程的同步问题,即多个线程不能操作同一个数据,否则会造成数据的不确定性。通过 threading 模块的 Lock 对象能够保证数据的正确性。

比如,使用多线程将抓取数据写入磁盘文件,此时,就要对执行写入操作的线程加锁,这样才能够避免写入的数据被覆盖。当线程执行完写操作后会主动释放锁,继续让其他线程去获取锁,周而复始,直到所有写操作执行完毕。具体方法如下所示:

  1. from threading import Lock
  2. lock = Lock()
  3. 获取锁

  4. lock.acquire()
  5. wirter.writerows(“线程锁问题解决”)
  6. 释放锁

  7. lock.release()

Queue队列模型

对于 Python 多线程而言,由于 GIL 全局解释器锁的存在,同一时刻只允许一个线程占据解释器执行程序,当此线程遇到 IO 操作时就会主动让出解释器,让其他处于等待状态的线程去获取解释器来执行程序,而该线程则回到等待状态,这主要是通过线程的调度机制实现的。

由于上述原因,我们需要构建一个多线程共享数据的模型,让所有线程都到该模型中获取数据。queue(队列,先进先出) 模块提供了创建共享数据的队列模型。比如,把所有待爬取的 URL 地址放入队列中,每个线程都到这个队列中去提取 URL。queue 模块的具体使用方法如下:

  1. 导入模块

  2. from queue import Queue
  3. q = Queue() #创界队列对象
  4. q.put(url) 向队列中添加爬取一个url链接
  5. q.get() # 获取一个url,当队列为空时,阻塞
  6. q.empty() # 判断队列是否为空,True/False

多线程爬虫案例

下面通过多线程方法抓取小米应用商店(https://app.mi.com/)中应用分类一栏,所有类别下的 APP 的名称、所属类别以及下载详情页 URL 。如下图所示:

image.png
图1:小米应用商城

抓取下来的数据 demo 如下所示:

**

三国杀,棋牌桌游,http://app.mi.com/details?id=com.bf.sgs.hdexp.mi

1) 案例分析

通过搜索关键字可知这是一个动态网站,因此需要抓包分析。

刷新网页来重新加载数据,可得知请求头的 URL 地址,如下所示:

**

https://app.mi.com/categotyAllListApi?page=0&categoryId=1&pageSize=30

其中查询参数 pageSize 参数值不变化,page 会随着页码的增加而变化,而类别 Id 通过查看页面元素,如下所示

    • 游戏
    • 实用工具
    • 影音视听
    • 聊天社交
    • 图书阅读
    • 学习教育
    • 效率办公
    • 时尚购物
    • 居家生活
    • 旅行交通
    • 摄影摄像
    • 医疗健康
    • 体育运动
    • 新闻资讯
    • 娱乐消遣
    • 金融理财

    因此,可以使用 Xpath 表达式匹配 href 属性,从而提取类别 ID 以及类别名称,表达式如下:

    **

    基准表达式:xpath_bds = '//ul[@class="category-list"]/li'
    提取 id 表达式:typ_id = li.xpath('./a/@href')[0].split('/')[-1]
    类型名称:typ_name = li.xpath('./a/text()')[0]
    

    点击开发者工具的 response 选项卡,查看响应数据,如下所示:

    **

    {
    count: 2000,
    data: [
    {
    appId: 1348407,
    displayName: "天气暖暖-关心Ta从关心天气开始",
    icon: "http://file.market.xiaomi.com/thumbnail/PNG/l62/AppStore/004ff4467a7eda75641eea8d38ec4d41018433d33",
    level1CategoryName: "居家生活",
    packageName: "com.xiaowoniu.WarmWeather"
    },
    {
    appId: 1348403,
    displayName: "贵斌同城",
    icon: "http://file.market.xiaomi.com/thumbnail/PNG/l62/AppStore/0e607ac85ed9742d2ac2ec1094fca3a85170b15c8",
    level1CategoryName: "居家生活",
    packageName: "com.gbtc.guibintongcheng"
    },
    ...
    ...
    

    通过上述响应内容,我们可以从中提取出 APP 总数量(count)和 APP (displayName)名称,以及下载详情页的 packageName。由于每页中包含了 30 个 APP,所以总数量(count)可以计算出每个类别共有多少页。

    **

    pages = int(count) // 30 + 1
    

    下载详情页的地址是使用 packageName 拼接而成,如下所示:

    **

    link = 'http://app.mi.com/details?id=' + app['packageName']
    

    ​2) 完整程序

    完整程序如下所示:

    1. -- coding:utf8 --

    2. import requests
    3. from threading import Thread
    4. from queue import Queue
    5. import time
    6. from fake_useragent import UserAgent
    7. from lxml import etree
    8. import csv
    9. from threading import Lock
    10. import json
    11. class XiaomiSpider(object):
    12. def init(self):
    13. self.url = ‘http://app.mi.com/categotyAllListApi?page={}&categoryId={}&pageSize=30’
    14. 存放所有URL地址的队列

    15. self.q = Queue()
    16. self.i = 0
    17. 存放所有类型id的空列表

    18. self.id_list = []
    19. 打开文件

    20. self.f = open(‘XiaomiShangcheng.csv’,‘a’,encoding=‘utf-8’)
    21. self.writer = csv.writer(self.f)
    22. 创建锁

    23. self.lock = Lock()
    24. def get_cateid(self):
    25. 请求

    26. url = ‘http://app.mi.com/’
    27. headers = { ‘User-Agent’: UserAgent().random}
    28. html = requests.get(url=url,headers=headers).text
    29. 解析

    30. parse_html = etree.HTML(html)
    31. xpath_bds = ‘//ul[@class=“category-list”]/li’
    32. li_list = parse_html.xpath(xpath_bds)
    33. for li in li_list:
    34. typ_name = li.xpath(‘./a/text()’)[0]
    35. typ_id = li.xpath(‘./a/@href’)[0].split(‘/’)[-1]
    36. 计算每个类型的页数

    37. pages = self.get_pages(typ_id)
    38. #往列表中添加二元组
    39. self.id_list.append( (typ_id,pages) )
    40. 入队列

    41. self.url_in()
    42. 获取count的值并计算页数

    43. def get_pages(self,typ_id):
    44. 获取count的值,即app总数

    45. url = self.url.format(0,typ_id)
    46. html = requests.get(
    47. url=url,
    48. headers={‘User-Agent’:UserAgent().random}
    49. ).json()
    50. count = html[‘count’]
    51. pages = int(count) // 30 + 1
    52. return pages
    53. url入队函数,拼接url,并将url加入队列

    54. def url_in(self):
    55. for id in self.id_list:
    56. id格式:(‘4’,pages)

    57. for page in range(1,id[1]+1):
    58. url = self.url.format(page,id[0])
    59. 把URL地址入队列

    60. self.q.put(url)
    61. 线程事件函数: get() -请求-解析-处理数据,三步骤

    62. def get_data(self):
    63. while True:
    64. 判断队列不为空则执行,否则终止

    65. if not self.q.empty():
    66. url = self.q.get()
    67. headers = {‘User-Agent’:UserAgent().random}
    68. html = requests.get(url=url,headers=headers)
    69. res_html = html.content.decode(encoding=‘utf-8’)
    70. html=json.loads(res_html)
    71. self.parse_html(html)
    72. else:
    73. break
    74. 解析函数

    75. def parse_html(self,html):
    76. 写入到csv文件

    77. app_list = []
    78. for app in html[‘data’]:
    79. app名称 + 分类 + 详情链接

    80. name = app[‘displayName’]
    81. link = ‘http://app.mi.com/details?id=’ + app[‘packageName’]
    82. typ_name = app[‘level1CategoryName’]
    83. 把每一条数据放到app_list中,并通过writerows()实现多行写入

    84. app_list.append([name,typ_name,link])
    85. print(name,typ_name)
    86. self.i += 1
    87. 向CSV文件中写入数据

    88. self.lock.acquire()
    89. self.writer.writerows(app_list)
    90. self.lock.release()
    91. 入口函数

    92. def main(self):
    93. URL入队列

    94. self.get_cateid()
    95. t_list = []
    96. 创建多线程

    97. for i in range(1):
    98. t = Thread(target=self.get_data)
    99. t_list.append(t)
    100. 启动线程

    101. t.start()
    102. for t in t_list:
    103. 回收线程

    104. t.join()
    105. self.f.close()
    106. print(‘数量:’,self.i)
    107. if name == ‘main’:
    108. start = time.time()
    109. spider = XiaomiSpider()
    110. spider.main()
    111. end = time.time()
    112. print(‘执行时间:%.1f’ % (end-start))

    运行上述程序后,打开存储文件,其内容如下:

    **

    在我们之间-单机版,休闲创意,http://app.mi.com/details?id=com.easybrain.impostor.gtx
    
    粉末游戏,模拟经营,http://app.mi.com/details?id=jp.danball.powdergameviewer.bnn
    
    三国杀,棋牌桌游,http://app.mi.com/details?id=com.bf.sgs.hdexp.mi
    
    腾讯欢乐麻将全集,棋牌桌游,http://app.mi.com/details?id=com.qqgame.happymj
    
    快游戏,休闲创意,http://app.mi.com/details?id=com.h5gamecenter.h2mgc
    
    皇室战争,战争策略,http://app.mi.com/details?id=com.supercell.clashroyale.mi
    
    地铁跑酷,跑酷闯关,http://app.mi.com/details?id=com.kiloo.subwaysurf
    ...
    ...
    

    零基础Python学习资源介绍

    👉Python学习路线汇总👈

    温馨提示:篇幅有限,已打包文件夹获取方式在:点击这里【 Python全套资料】 即可获取。

    image.png

    👉Python必备开发工具👈

    image.png

    温馨提示:篇幅有限,已打包文件夹获取方式在:点击这里【 Python全套资料】 即可获取。

    👉Python学习视频600合集👈

    image.png
    观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

    👉实战案例👈

    image.png

    光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

    👉100道Python练习题👈检查学习结果。

    image.png

    👉面试刷题👈

    image.png

    温馨提示:篇幅有限,已打包文件夹获取方式在:点击这里【 Python全套资料】 即可获取。

    本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

    Python的多线程爬虫详解 的相关文章

    • Linux usb 2. 协议分析

      文章目录 0 背景1 USB 协议传输格式1 1 Packet1 1 1 Token Packet1 1 2 Data Packet1 1 3 Handshake Packet1 1 4 Special Packet 1 2 Transac
    • RISCV 入门 (学习笔记)

      文章目录 1 risv 相关背景1 1 arm 授权费1 2 riscv 发展历史1 3 riscv 风险 2 指令集2 1 可配置的通用寄存器组2 2 规整的指令编码2 3 简洁的存储器访问指令2 4 高效的分支跳转指令2 5 简洁的子程
    • Linux usb 1. 总线简介

      文章目录 1 USB 发展历史1 1 USB 1 0 2 01 2 USB 3 01 3 速度识别1 4 OTG1 5 phy 总线1 6 传输编码方式 2 总线拓扑2 1 Device 内部的逻辑关系2 2 Compound Compos
    • Linux usb 3. Host 详解

      文章目录 1 简介2 Usb Core 驱动设备模型2 1 Usb Device Layer2 1 1 device struct usb device 2 1 2 driver struct usb device driver 2 1 3
    • Linux usb 4. Device 详解

      文章目录 1 简介2 Platform Layer2 1 Platform Device2 2 Platform Driver 3 UDC Gadget Layer3 1 Gadget Bus3 2 Gadget Device3 2 1 E
    • Linux USB (目录)

      1 USB 总线简介 2 USB 协议分析 3 USB Host 详解 4 USB Device 详解 5 usbip USB Over IP 使用实例 6 USB HC UDC 测试 7 Linux 配置 ADBD
    • Linux usb 5. usbip (USB Over IP) 使用实例

      文章目录 0 简介1 Server 配置2 Client 配置参考资料 0 简介 USB Over IP 是一种应用很多的场景 xff0c 目前已经有现成的解决方案 usbip linux 和 windows 环境下都有配套软件 xff0c
    • 最全随机抽样算法(从N个数中抽取M个等)集合

      项目github地址 xff1a bitcarmanlee easy algorithm interview and practice 欢迎大家star xff0c 留言 xff0c 一起学习进步 1 从N个数中等概率抽取M个数 从N个样本
    • Linux usb 6. HC/UDC 测试

      文章目录 1 背景介绍2 Device gadget zero 2 1 96 gadget zero 96 创建2 2 SourceSink Function2 3 Loopback Function 3 Host usbtest ko 3
    • Linux usb 7. Linux 配置 ADBD

      文章目录 1 简介2 ADBD 源码3 Gadget Device 配置3 1 functionfs3 2 legacy 方式配置 functionfs3 3 configfs 方式配置 functionfs3 4 adb 使用配置 参考资
    • HW-RTOS 概述

      文章目录 1 背景介绍1 1 OS 实时难题1 2 Linux 实时补丁1 3 Xenomai 43 Linux 双内核1 4 HW RTOS1 5 More 2 优化点1 xff1a API2 1 原理介绍2 1 1 Software A
    • RISCV MMU 概述

      1 背景简介 Linux 内存管理包含很多内容 xff0c 主要知识点可以参考 Linux Mem 本文只描述其中的一个知识点 Paging and MMU 本文以全志 D1 为例 xff0c 包含了平头哥出品的一颗 Riscv64 的 C
    • 主流 RTOS 评估

      1 RT Thread RT Thread 是国内出产的一款非常优秀的 RTOS 它和 FreeRTOS uCos 等经典 RTOS 最大的不同是 xff1a 它不仅仅是一个实时内核 xff0c 还具备丰富的中间层组件 它提供了一个完整的软
    • Linux mem 2.8 Kfence 详解

      1 原理介绍 Kfence Kernel Electric Fence 是 Linux 内核引入的一种低开销的内存错误检测机制 xff0c 因为是低开销的所以它可以在运行的生产环境中开启 xff0c 同样由于是低开销所以它的功能相比较 KA
    • Linux Phy 驱动解析

      文章目录 1 简介2 phy device2 1 mdio bus2 2 mdio device2 3 mdio driver2 4 poll task2 4 1 自协商配置2 4 2 link 状态读取2 4 3 link 状态通知 3
    • 程序媛工作几年后的感受!体验?

      黑客技术 点击右侧关注 xff0c 了解黑客的世界 xff01 Java开发进阶 点击右侧关注 xff0c 掌握进阶之路 xff01 Python开发 点击右侧关注 xff0c 探讨技术话题 xff01 作者 xff1a hq nuan 来
    • ubuntu 通过 apt-get 安装软件失败时的解决方案

      最近在 vmware上的ubuntu系统下安装 软件时出现安装失败情况 xff0c 在网上搜了一通 xff0c 终于找到了解决方案 遇到的问题和解决方案如下 xff1a 一 apt get install vim二 apt get upda
    • JAVA自学之路 三:要动手

      原创 尚学堂科技 马士兵老师 JAVA自学之路 三 要动手 转载请注明出处 http www bjsxt com zixue zixuezhilu 3 html 无论如何 xff0c 请坚持不懈的动手实验 xff01 学习Java要动手 x
    • Eigen库的安装

      运行命令 xff1a sudo apt get install libeigen3 dev 假设默认安装到 usr local include里 可在终端中输入locate eigen3查看位置 xff0c 若实际中默认安装到了 usr i
    • 搭建自己的简易服务器(公网)

      大部分时候做嵌入式开发的 xff0c 如果是wifi 可以工作在局域网 xff0c 至于物联网设备 xff0c 插手机卡的那种就需要公网ip 测试起来相对比较麻烦 xff0c 电信宽带用户有的可以映射使用 xff0c 但是ip会改变 xff

    随机推荐