爬虫实践之线程池与多线程爬取菜价信息

695 阅读4分钟

爬虫实践之线程池与多线程

1.首先,选择想要爬取的目标网站

例:我选择的是北京新发地(xinfadi.com.cn/priceDetail…) 上面有蔬菜的实时价格,来看一下页面。

image.png

简单的爬虫爬取页面的内容的方法有好几种,简单列举两点:

  • 使用request库获取网页源码,然后使用etree进行xpath解析或者beautiful解析文本
  • 如果网站使用的是ajax加载,可以找到网页的通讯请求,直接通过请求获取通讯数据包,然后直接分析数据包的内容

2.分析页面类型

因为我们爬取的内容为分页展示,所以首先我们需要知道网页翻页使用的是重定位还是ajax局部刷新,我们翻到第二页来看,看看网址是否发生改变:

image.png 可以清晰的看到,网址没有任何变化,这个时候,传统的request+etree来获取数据会有一点困难,因为网址没变,只是ajax进行了刷新,故可以开始尝试抓包。

点击F12进入调试模式,不用刷新页面,然后我们点击下面的翻页栏,例:点击第5页

image.png 我们可以看到网页给服务器发了一个请求,点击查看具体的请求内容

image.png 里面有一个url,我们复制这个url,新打开一个页面,输入这个网址

image.png 发现里面就是服务器发回来的json包,是用来更新网页ajax渲染的部分。

但是,有一个问题,这个json文件的第一个属性,current=1,返回的也是第1页的内容,但是网页只发了这一个请求啊,我们点击的是第五页,而且页面上刷新出来的也是第五页的内容,那为什么信息不对呢?而且既然url中没包含页面信息,那服务器是怎么知道我点击了第几页,从而返回正确的信息呢?

实际上,请求中并不是没包含这些信息,只不过它隐藏起来了。我们看一下网络监听中的负载信息

image.png 网页发送请求的同时,也把表单中的这些信息一并发回去了,理解了这个,那我们怎么实现对于不同页的接口访问呢,其实很简单,在url后加上 ?current=5 就可以访问到第五页的json通讯文件了,其他页面只需要改动这个数字即可。

image.png

3.线程池+多线程的爬取

确定了页面的类型和爬取的方法,那么可以开始写代码了。json文件的处理不多赘述,比较简单,另headers此网站不需要,其他网站按需进行添加属性。

因为需要爬取的页面均为一种类型,可以不用继承的线程类,直接直接定义方法,通过线程池的调用,实现多线程。

通过ThreadPoolExecutor创建线程池,参数代表线程池的大小,即同时运行多少个线程。

通过t.submit(方法,方法所需的参数(可以为多个))启动线程

from threading import Lock, Thread
from itsdangerous import json
import requests
import csv
from concurrent.futures import ThreadPoolExecutor

def get_price(url,page_info):
    headers = {}
    resp = requests.get(url,headers)
    res = json.loads(resp.text)

    price_list = res['list']
    for i in price_list:
        name = i['prodName']
        avgprice = i['avgPrice']
        detail = [name] + [avgprice]
        with open('菜价.csv','a+',encoding='utf-8-sig',newline='') as fi:
            fi = csv.writer(fi)
            fi.writerow(detail)
        print('第{}页爬取成功!'.format(page_info))
            

if __name__ == '__main__':
    with ThreadPoolExecutor(50) as t:
        for i in range(1,200):
            t.submit(get_price,url = 'http://xinfadi.com.cn/getPriceData.html?current={}'.format(i),page_info = i)

写完程序,我们来看看运行结果怎么样:

image.png 好像都没问题,但是,我们打开文件看一下

image.png 多线程的问题之一,如何处理共享资源的问题暴露出来,最简单的处理方式就是加锁,在一个进程写入的时候,不允许其他线程对文件进行写入或修改,这样的锁叫做互斥锁,其他种类的锁这里就不介绍了,感兴趣的可以自己去查一下。

看一下加了互斥锁后的代码

from threading import Lock, Thread
from itsdangerous import json
import requests
import csv
from concurrent.futures import ThreadPoolExecutor

def get_price(url,page_info):
    headers = {}
    resp = requests.get(url,headers)
    res = json.loads(resp.text)

    price_list = res['list']
    with lock:
        for i in price_list:
            name = i['prodName']
            avgprice = i['avgPrice']
            detail = [name] + [avgprice]
            with open('菜价.csv','a+',encoding='utf-8-sig',newline='') as fi:
                fi = csv.writer(fi)
                fi.writerow(detail)
            print('第{}页爬取成功!'.format(page_info))
            

if __name__ == '__main__':
    lock = Lock()
    with ThreadPoolExecutor(50) as t:
        for i in range(1,200):
            t.submit(get_price,url = 'http://xinfadi.com.cn/getPriceData.html?current={}'.format(i),page_info = i)

爬下来的数据,看起来没有啥子问题。

image.png