爬虫第十式:多线程爬取小米应用商店聊天社交类别

news/2024/7/19 8:41:21 标签: python, 爬虫

温馨提示:

爬虫玩得好,监狱进得早。数据玩得溜,牢饭吃个够。

《刑法》第 285 条,非法获取计算机信息系统数据罪。
       违反国家规定,侵入前款规定以外的计算机信息系统或者采用其他技术手段,获取该计算机信息系统中存储、处理或者传输的数据,或者对该计算机信息系统实施非法控制,情节严重的,处三年以下有期徒刑或者拘役,并处或者单处罚金;情节特别严重的,处三年以上七年以下有期徒刑,并处罚金。

正文:
本章我们来介绍一个新的方式爬虫实用知识, 多线程 ,可能有人会问为什么不用多线程,这个其实说实话,多线程用的会多一点,进程用的少,多线程为主

应用场景

python">【1】多进程:CPU密集程序
【2】多线程:爬虫(网络I/O)、本地磁盘I/O

创建 队列,等待线程进入

python">【1】导入模块 
	from queue import Queue 
【2】使用 
	q = Queue()
	q.put(url) 
	q.get() # 当队列为空时,阻塞 
	q.empty() # 判断队列是否为空, True/False3】q.get()解除阻塞方式 
	3.1) q.get(block=False) 
	3.2) q.get(block=True,timeout=3) 
	3.3) if not q.empty(): 
			q.get()

创建线程模块

python"># 导入模块
from threading import Thread
 
# 使用流程 
t = Thread(target=函数名) # 创建线程对象 
t.start() # 创建并启动线程 
t.join() # 阻塞等待回收线程

# 如何创建多线程 
t_list = []

for i in range(5):
	t = Thread(target=函数名) 
	t_list.append(t) 
	t.start()
	
for t in t_list: 
	t.join()

现在我们分析一下小米应用商店的数据,先来打开看一下:
在这里插入图片描述
这个网站是一个动态加载的网站,我们上一个案例 豆瓣电影排行榜数据抓取
提到过,这样网站的特点以及怎么抓取数据,这里我们直接进行抓取,没必要观察URL地址的规律,因为响应内容不存在,我们直接 F12 就抓包就好了
在这里插入图片描述

我们先来刷新一下,然后点击下一页,多点两下,抓取几个包就够我们用了

在这里插入图片描述
这个就是我们抓取的异步的网络数据包,我们从表面就能看到,问好后面的page= 有0、1、2、3… 这个就是一个查询参数,那我们点开一个看看:

在这里插入图片描述
这个应该是,这是count有两千个应该是社交app的个数,下面我们也看到了各个应用app的名字,接下来我们换到Headers里面分析各个信息:

在这里插入图片描述

这里面我们分析到这个是一个GET请求,那是这样的请求的话我们就直接找URL、headers、Query String Parameters这三个东西,现在URL就是我们找到的框框里面的URL,headers就是Request Headers的信息,最后我们看一下Query String Parameters这里面的东西
在这里插入图片描述

这三个,看着也没有加密的东西,也没有时间戳的字符串
page应该是页数
categoryld常规来说这个是类别应用的id
pageSize这个是每页的应用个数

那看一下接下里的数据包
在这里插入图片描述
在这里插入图片描述
我们看到就是page变了,其他的都是一样的,接下来我们复制抓到的URL地址到浏览器看一下:
在这里插入图片描述
那我们就找到了动态加载的数据了,那我们就写写代码吧,URL的话就只是把page后面直接改成花括号就行

导入模块

python">import requests 
import json
from threading import Thread, Lock  # 线程模块和线程锁 
from queue import Queue  # 创建队列
import time
from fake_useragent import UserAgent

定义功能函数,减少重复代码

python">class XiaomiSpider:
    def __init__(self):
        self.url = 'http://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30'
        # 队列 锁
        self.q = Queue()
        self.lock = Lock()

再来一个函数,就是把生成的URL地址,入到队列里面,等待抓取

python">def url_to_queue(self):
	# 生成所有待抓取的URL地址
    for page in range(67):
        page_url = self.url.format(page)
        # 入队列
        self.q.put(page_url)

接下来就是创建 线程事件函数 从队列获取地址,请求,解析,数据处理,那我们思维谨慎一点,先来个判断,看看队列里面有没有数据,不是空的就拿出来

python">if not self.q.empty():
	# 获取
	url = self.q.get()

下面是取出数据后就是开始请求,解析,数据处理

python">	html = self.get_html(url=url)
	html = json.loads(html)
	for one_app_dict in html['data']:
	    item = {}
	    item['name'] = one_app_dict['displayName']
	    item['type'] = one_app_dict['level1CategoryName']
	    item['link'] = one_app_dict['packageName']
	    print(item)

创建run函数,把程序运行起来

python">def run(self):
    # URL地址入队列
    self.url_to_queue()
    # 创建多线程
    t_list = []
    # 线程个数
    for i in range(1):
        t = Thread(target=self.parse_html)
        t_list.append(t)
        t.start()

    for t in t_list:
        t.join()

这样代码就大体写完了,但是还是有点小问题,我们这个直接t.start开始后就直接到线程事件函数里面,这里面来之后,我们直接一个线程过来了,判断是不是空的就只是get出一个地址,然后在发请求,解析,数据处理后面没有代码直接结束了

也就是说我就获取一个地址让一个线程,进行抓取,这样的话是不对的吧,因为假如我们有几万页的地址,我们不能创建一万个线程吧,所以我们应该让一个线程抓完之后再继续抓其他的,不是停下来不干活了,所以我们直接给他一个while循环

python">while True:
    if not self.q.empty():
        url = self.q.get()
        html = self.get_html(url=url)
        html = json.loads(html)
        for one_app_dict in html['data']:
            item = {}
            item['name'] = one_app_dict['displayName']
            item['type'] = one_app_dict['level1CategoryName']
            item['link'] = one_app_dict['packageName']
            print(item)
	else:
	    break

这样我们的多线程就是实现了,但是还有一个东西就是我们还是那面创建的线程锁没有用,现在我们也用上它,但是我得考虑一下在哪里加锁,那就看看我们在哪里操作全局变量啊,是不是在这个线程事件函数这里操作啊,是不是在这个上面操作的,或者说几乎同时操作的,
在这里插入图片描述
在这里举个例子:

python">self.q: ['http://page88.html']
Thread-1: if条件成立,进入分支语句
Thread-2: if条件也成立,也进入分支语句

像这样,就是当有最后一个地址的时候,线程1 if 判断,里面有,不是空的,直接进入分支语句,马上就要get获取,就在这时候,线程2 也是 if 判断了,也是判断到里面不是空的,线程2也就是进入分支,最后线程1和线程2都会执行get()语句,但是有一个会发生阻塞,那我们怎么不然它阻塞,只能是加锁控制

python">while True:
    self.lock.acquire()
    if not self.q.empty():
        url = self.q.get()
        print(url)
        
        # 释放锁
        self.lock.release()
        html = self.get_html(url=url)
        html = json.loads(html)
        for one_app_dict in html['data']:
            item = {}
            item['name'] = one_app_dict['displayName']
            item['type'] = one_app_dict['level1CategoryName']
            item['link'] = one_app_dict['packageName']
            print(item)
    else:
        # 释放锁
        self.lock.release()
        break

这样的加锁和释放锁的目的是
加锁一定对应着释放锁,如果进入到if里面了,就在if里面释放锁,如果没有进入到if里面,那就一定会走else里,那就在else里释放锁

最后将全部代码奉上:

python">import requests
import json
from threading import Thread, Lock
from queue import Queue
import time
from fake_useragent import UserAgent

class XiaomiSpider:
    def __init__(self):
        self.url = 'http://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30'
        # 队列 锁
        self.q = Queue()
        self.lock = Lock()

    def get_html(self, url):
        headers = {'User-Agent':UserAgent().random}
        html = requests.get(url=url,headers=headers).text

        return html

    def url_to_queue(self):
        for page in range(67):
            page_url = self.url.format(page)
            self.q.put(page_url)

    def parse_html(self):
        while True:
            self.lock.acquire()
            if not self.q.empty():
                url = self.q.get()
                print(url)
                # 释放锁
                self.lock.release()
                html = self.get_html(url=url)
                html = json.loads(html)
                for one_app_dict in html['data']:
                    item = {}
                    item['name'] = one_app_dict['displayName']
                    item['type'] = one_app_dict['level1CategoryName']
                    item['link'] = one_app_dict['packageName']
                    print(item)
            else:
                # 释放锁
                self.lock.release()
                break

    def run(self):
        # URL地址入队列
        self.url_to_queue()
        # 创建多线程
        t_list = []
        for i in range(1):
            t = Thread(target=self.parse_html)
            t_list.append(t)
            t.start()

        for t in t_list:
            t.join()

if __name__ == '__main__':
    start = time.time()
    spider = XiaomiSpider()
    spider.run()
    end = time.time()
    print('time:%.2f' % (end - start))

http://www.niftyadmin.cn/n/835446.html

相关文章

Ubuntu18.04使用docker

1、部署docker:apt install docker.iodocker -v #查看docker版本,一般都是最新的。2、查看docker版本rootkaili:~# docker -vDocker version 18.06.1-ce, build e68fc7ace代表社区版,EE为企业版3、利用docker部署gitlab创建容器外挂目录gitla…

SpringMVC—接收请求参数和页面传参

SpringMVC—接收请求参数和页面传参 1.使用HttpServletRequest获取 RequestMapping("/test.do") public String test(HttpServletRequest request){ String name request.getParameter("name") String pass request.getParameter("pass") …

JavaScript核心属性-数据类型

原始类型 原始类型,又称原始值,是直接代表JavaScript语言实现的最底层数据。原始类型分为 boolean类型、number类型、string类型三种。声明变量并初始化值为原始类型,一般称之为字面量方式定义变量,或直接量方式定义变量。 number…

SSM框架—详细整合教程(Spring+SpringMVC+MyBatis)

SSM框架——详细整合教程(SpringSpringMVCMyBatis) (转载自: http://blog.csdn.net/zhshulin/article/details/37956105?utm_sourcetuicool ) 目录(?)[] 使用SSM(Spring、SpringMVC和Mybatis)…

selenium自动化测试工具加百度小案例

seleniumPhantomJS/Chrome/Firefox selenium 【1】定义1.1) 开源的Web自动化测试工具【2】用途2.1) 对Web系统进行功能性测试,版本迭代时避免重复劳动2.2) 兼容性测试(测试web程序在不同操作系统和不同浏览器中是否运行正常)2.3) 对web系统进行大数量测试【3】特点3.1) 可根据…

ArcGIS 10.2字段计算器Field Calculator批量条件赋值用法总结

花了几个小时专研这个批量处理,由于本人愚钝,所以费的时间较长,在网上搜的话,可以看到一大堆字段计算器按条件赋值的结果,但是就是没有人测试条件是汉字的哪一类。我利用网上的答案,就是直接用的一个if语句…

MyBatis学习总结——Mybatis3.x与Spring4.x整合

本文转载自: 孤傲苍狼 http://www.cnblogs.com/xdp-gacl/p/4271627.html点击打开链接MyBatis学习总结(八)——Mybatis3.x与Spring4.x整合 一、搭建开发环境 1.1、使用Maven创建Web项目 执行如下命令: mvn archetype:create -DgroupIdme.gacl -DartifactI…

技本功丨技能get,React的优雅升级!

今日,我们不啖鸡汤,不饮鸡血 只有干货——关于React的优雅升级 双手奉上,来,干了! -2019年第4期- 夫 子 说 本次升级基础包情况:react 15.6 -> 16.6 升级流程: 1、升级React 2、功能测试 OK&…