多线程爬取小米应用商店

目标html

1、网址 :百度搜 - 小米应用商店,进入官网
2、目标 :全部应用分类
   应用名称
   应用连接

实现步骤python

  • 一、确认是否为动态加载
1、页面局部刷新
2、右键查看网页源代码,搜索关键字未搜到
# 此网站为动态加载网站,须要抓取网络数据包分析
  • 二、F12抓取网络数据包
1、抓取返回json数据的URL地址(Headers中的Request URL)
   http://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30
        
2、查看并分析查询参数(headers中的Query String Parameters)
   page: 1
   categoryId: 2
   pageSize: 30
   # 只有page在变,0 1 2 3 ... ... ,这样咱们就能够经过控制page的值拼接多个返回json数据的URL地址
  • 将抓取数据保存到csv文件
# 注意多线程写入的线程锁问题
from threading import Lock
lock = Lock()
# 加锁
lock.acquire()
python语句
# 释放锁
lock.release()
  • 总体思路
1、在 __init__(self) 中建立文件对象,多线程操做此对象进行文件写入
  self.f = open('xiaomi.csv','a',newline='')
  self.writer = csv.writer(self.f)
  self.lock = Lock()
2、每一个线程抓取1页数据后将数据进行文件写入,写入文件时须要加锁
  def parse_html(self):
    app_list = []
    for xxx in xxx:
        app_list.append([name,link,typ])
    self.lock.acquire()
    self.wirter.writerows(app_list)
    self.lock.release()
3、全部数据抓取完成关闭文件
  def main(self):
    self.f.close()
  • 代码实现
import requests
from threading import Thread
from queue import Queue
import time
from useragents import ua_list
from lxml import etree
import csv
from threading import Lock
import random

class XiaomiSpider(object):
  def __init__(self):
    self.url = 'http://app.mi.com/categotyAllListApi?page={}&categoryId={}&pageSize=30'
    # 存放全部URL地址的队列
    self.q = Queue()
    self.i = 0
    # 存放全部类型id的空列表
    self.id_list = []
    # 打开文件
    self.f = open('xiaomi.csv','a')
    self.writer = csv.writer(self.f)
    # 建立锁
    self.lock = Lock()


  def get_cateid(self):
    # 请求
    url = 'http://app.mi.com/'
    headers = { 'User-Agent': random.choice(ua_list)}
    html = requests.get(url=url,headers=headers).text
    # 解析
    parse_html = etree.HTML(html)
    xpath_bds = '//ul[@class="category-list"]/li'
    li_list = parse_html.xpath(xpath_bds)
    for li in li_list:
      typ_name = li.xpath('./a/text()')[0]
      typ_id = li.xpath('./a/@href')[0].split('/')[-1]
      # 计算每一个类型的页数
      pages = self.get_pages(typ_id)
      self.id_list.append( (typ_id,pages) )

    # 入队列
    self.url_in()

  # 获取counts的值并计算页数
  def get_pages(self,typ_id):
    # 每页返回的json数据中,都有count这个key
    url = self.url.format(0,typ_id)
    html = requests.get(
      url=url,
      headers={'User-Agent':random.choice(ua_list)}
    ).json()
    count = html['count']
    pages = int(count) // 30 + 1

    return pages

  # url入队列
  def url_in(self):
    for id in self.id_list:
      # id为元组,('2',pages)
      for page in range(1,id[1]+1):
        url = self.url.format(page,id[0])
        # 把URL地址入队列
        self.q.put(url)

  # 线程事件函数: get() - 请求 - 解析 - 处理数据
  def get_data(self):
    while True:
      if not self.q.empty():
        url = self.q.get()
        headers = {'User-Agent':random.choice(ua_list)}
        html = requests.get(url=url,headers=headers).json()
        self.parse_html(html)
      else:
        break

  # 解析函数
  def parse_html(self,html):
    # 存放1页的数据 - 写入到csv文件
    app_list = []

    for app in html['data']:
      # 应用名称 + 连接 + 分类
      name = app['displayName']
      link = 'http://app.mi.com/details?id=' + app['packageName']
      typ_name = app['level1CategoryName']
      # 把每一条数据放到app_list中,目的为了 writerows()
      app_list.append([name,typ_name,link])

      print(name,typ_name)
      self.i += 1

    # 开始写入1页数据 - app_list
    self.lock.acquire()
    self.writer.writerows(app_list)
    self.lock.release()

  # 主函数
  def main(self):
    # URL入队列
    self.get_cateid()
    t_list = []
    # 建立多个线程
    for i in range(1):
      t = Thread(target=self.get_data)
      t_list.append(t)
      t.start()

    # 回收线程
    for t in t_list:
      t.join()

    # 关闭文件
    self.f.close()
    print('数量:',self.i)

if __name__ == '__main__':
  start = time.time()
  spider = XiaomiSpider()
  spider.main()
  end = time.time()
  print('执行时间:%.2f' % (end-start))