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

温馨提示:

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

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

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

应用场景

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

创建 队列,等待线程进入

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()

创建线程模块

# 导入模块
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()

现在我们分析一下小米应用商店的数据,先来打开看一下:
爬虫第十式:多线程爬取小米应用商店聊天社交类别_第1张图片
这个网站是一个动态加载的网站,我们上一个案例 豆瓣电影排行榜数据抓取
提到过,这样网站的特点以及怎么抓取数据,这里我们直接进行抓取,没必要观察URL地址的规律,因为响应内容不存在,我们直接 F12 就抓包就好了
爬虫第十式:多线程爬取小米应用商店聊天社交类别_第2张图片

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

爬虫第十式:多线程爬取小米应用商店聊天社交类别_第3张图片
这个就是我们抓取的异步的网络数据包,我们从表面就能看到,问好后面的page= 有0、1、2、3… 这个就是一个查询参数,那我们点开一个看看:

爬虫第十式:多线程爬取小米应用商店聊天社交类别_第4张图片
这个应该是,这是count有两千个应该是社交app的个数,下面我们也看到了各个应用app的名字,接下来我们换到Headers里面分析各个信息:

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

这里面我们分析到这个是一个GET请求,那是这样的请求的话我们就直接找URL、headers、Query String Parameters这三个东西,现在URL就是我们找到的框框里面的URL,headers就是Request Headers的信息,最后我们看一下Query String Parameters这里面的东西
爬虫第十式:多线程爬取小米应用商店聊天社交类别_第6张图片

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

那看一下接下里的数据包
爬虫第十式:多线程爬取小米应用商店聊天社交类别_第7张图片
爬虫第十式:多线程爬取小米应用商店聊天社交类别_第8张图片
我们看到就是page变了,其他的都是一样的,接下来我们复制抓到的URL地址到浏览器看一下:
爬虫第十式:多线程爬取小米应用商店聊天社交类别_第9张图片
那我们就找到了动态加载的数据了,那我们就写写代码吧,URL的话就只是把page后面直接改成花括号就行

导入模块

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()

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

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

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

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)

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

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循环

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

这样我们的多线程就是实现了,但是还有一个东西就是我们还是那面创建的线程锁没有用,现在我们也用上它,但是我得考虑一下在哪里加锁,那就看看我们在哪里操作全局变量啊,是不是在这个线程事件函数这里操作啊,是不是在这个上面操作的,或者说几乎同时操作的,
爬虫第十式:多线程爬取小米应用商店聊天社交类别_第10张图片
在这里举个例子:

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

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

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里释放锁

最后将全部代码奉上:

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))

你可能感兴趣的:(爬虫,python,爬虫)