如何获取掌上高考(招生计划)信息

爬取掌上高考(招生计划)

厦门航空没有反爬机制,较为简单(返回数据较为复杂),现在我们爬取掌上高考作为练手项目,相较于网上爬取豆瓣电影以及其他小demo,这个项目复杂的多。掌上高考有复杂的反爬机制,非常适合练手,同时可以自己整理自己的框架,这样针对下一个爬虫任务来说,只需要做小部分的改动就行。

1.抓包

请求网址:2024河南大学分数线_招生计划|掌上高考 (gaokao.cn)

通过点击下一页,分析返回的数据以及数据响应。

此时为我们需要的数据,分析请求头:

我们可以对url进行拼接,得到我们需要的数据,比如说更换学校id就可以查询不同的学校。再观察请求数据,如下:

后面发现这个data并没有什么暖用,不需要对其进行构造,就算构造以后也会被url查询字符串给覆盖,因此我们仅仅需要构造url就行

2.发起请求

配置请求头,请求数据和url之后,就可以直接发起请求了,构造请求数据

def get_config(page,id):
    # 打开你的json文件
    date = {"local_batch_id":7,"local_province_id":"41","local_type_id":"2","page":1,"school_id":"459","signsafe":"bbddec69cb7fe50f4d9ea21404d72fcf","size":10,"special_group":"","uri":"apidata/api/gkv3/plan/school","year":2023}
    herders = {"Accept":"application/json, text/plain, */*",
               "User-Agent": UserAgent().random}
    url = "https://api.eol.cn/web/api/?local_batch_id=7&local_province_id=41&local_type_id=1&page={}&school_id={}&size=10&special_group=&uri=apidata/api/gkv3/plan/school&year=2023".format(page,id)
    return date,herders,url

发起请求

def request_api(url, params, header):
    try:
        response = requests.post(url, json=params, headers=header)
        if response.status_code == 200:
            return response.json()
        else:
            print(f"请求失败,状态码:{response.status_code}")
            return None
    except requests.exceptions.RequestException as e:
        print(f"请求出现异常:{e}")
        return None

成功获取到数据,获取数据如下

分析发现,一次请求只能获取到10行数据,因此我们需要对其进行翻页处理,分析返回的数据携带数据的总条数,因此,我们可以根据这个总条数进行翻页处理,需要再次发送请求((可以修改size这个参数,减少请求次数,再提高效率的同时,做到反爬效果,但是仍需要动态的获取页数))

翻页处理

    result_numFound = result['numFound'] #获取总条数
    page_list = [i + 1 for i in range(int(result_numFound / 10) + 1)] #构造页码,用作参数重新构造请求头
    #循环发起请求
    for page in page_list:
        result_page = date, herders,url= get_config(page,i)

3.提取数据

同样根据自己的需要提取数据,我提取的数据如下:

data1 = {}
data1['name'] = result[i]['name']
data1['province_name'] = result[i]['province_name']
data1['num'] = result[i]['num']
data1['spname'] = result[i]['spname']
data1['spcode'] = result[i]['spcode']
data1['length'] = result[i]['length']
data1['level2_name'] = result[i]['level2_name']
data1['local_batch_name'] = result[i]['local_batch_name']
data1['local_type_name'] = result[i]['local_type_name']

此时已经完成了单页面的数据爬取,效果如下:

4.循环采集多所学校

通过构造不同的学校id对学校进行循环爬取,将学校id以参数的方式输入

 url = "https://api.eol.cn/web/api/?local_batch_id=7&local_province_id=41&local_type_id=1&page={}&school_id={}&size=10&special_group=&uri=apidata/api/gkv3/plan/school&year=2023".format(page,id)

其中page是页面,id是学校id

5.多线程爬取

将前面的爬虫封装为单个函数,同时将循环爬取学校提取到多线程函数里面,将学校id再线程池里面提交,结果如下:

def spider(i):
    #读取参数
    date, herders,url= get_config(1,i)
    proxy = get_proxy().get("proxy")
    result = request_api(url,date,herders,proxy)['data']
    result_numFound = result['numFound']
    result_item = result['item']
    page_list = [i + 1 for i in range(int(result_numFound / 10) + 1)]
    for page in page_list:
        result_page = date, herders,url= get_config(page,i)
        result = request_api(url, date, herders,proxy)['data']['item']
        get_data(temp,result)
    index = 0
 
max_workers = 10
t = []
t1 = time.time()
threadPool = ThreadPoolExecutor(max_workers)  # 创建最大线程数为

for i in range(32,34):  # 循环向线程池中提交task任务
    future = threadPool.submit(spider, i)
    t.append(future)
    # 若不需要获取返回值,则可不需要下面两行代码
    for i in t:
        print(i.result())  # 获取每个任务的返回值,result()会阻塞主线程

        threadPool.shutdown()  # 阻塞主线程,所有任务执行完后关闭线程池

此时可以实现多线程爬取,可以先把循环写小进行测试,可以很快的爬取数据,不要写大了,容易被封!!

6.反爬策略

  • 添加伪装头
  • 不仅仅需要一个伪装头就行,要保证每一次请求头都尽量不同,因此我们可以网上收集一部分伪装头,然后随机的读取,或者使用工具类的方法:

    from fake_useragent import UserAgent
    "User-Agent": UserAgent().random
    

    这样比较简单,也可自己写一个工具类

  • 设置随机休眠时间
  • 如果每一次请求都睡眠相同的时间(请求的时间不相同),会导致后台监控我们像爬虫程序,因此我们需要每一次请求的时间进行随机休眠。设置方法如下:

    time.sleep(random.uniform(1,3))
    

    7.IP代理技术

    上面的反爬策略仅仅能缓解一部分反爬机制,最核心的还是需要使用IP代理技术,如果使用IP代理?可以再网上找一些免费的代理,但是一般免费的成功率都很低,而收费的都很贵,因此我们都不采用。

    我们可以使用网上的IP代理池项目ProxyPool ,来动态的获取IP,实测效果很不错。

  • 下载项目
  • git clone git@github.com:jhao104/proxy_pool.git
    
  • 安装依赖
  • pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
    

    最好加上镜像,否则很慢。其他镜像地址如下:

    https://pypi.tuna.tsinghua.edu.cn/simple 清华
    https://pypi.mirrors.ustc.edu.cn/simple  中科大
    http://pypi.douban.com/simple/  豆瓣
    https://mirrors.aliyun.com/pypi/simple/ 阿里镜像
    http://pypi.hustunique.com/  华中科大
    https://mirror.baidu.com/pypi/simple  百度
    
  • 修改配置文件
  • # setting.py 为项目配置文件
    
    # 配置API服务
    
    HOST = "0.0.0.0"               # IP
    PORT = 5000                    # 监听端口
    
    
    # 配置数据库
    
    DB_CONN = 'redis://:pwd@127.0.0.1:8888/0'
    
    # 配置 ProxyFetcher
    
    PROXY_FETCHER = [
        "freeProxy01",      # 这里是启用的代理抓取方法名,所有fetch方法位于fetcher/proxyFetcher.py
        "freeProxy02",
        # ....
    ]
    

    主要修改的几项配置是监听端口(PORT)、 Redis 数据库的配置(DB_CONN)和启用的代理方法名(PROXY_FETCHER)。

    需要安装redis环境,存储再redis,可以使用docker随便装一个,百度一下教程!!

  • 启动项目
  • 这个项目总体分为两个部分:爬取代理 IP 和 取用代理 IP。

    如果你要启用爬取代理 IP 的服务,直接运行下面命令:

    python proxyPool.py schedule
    

    其实,作者在这个项目中运用的原来就是到一些免费的代理网站采集 IP,然后测试 IP 的可用性,可用的就存入 Redis 中,不可用就丢弃。

  • 使用代理 IP
  • python proxyPool.py server
    

    会使用flask搭建一个简单的服务,还需要自己手动封装一下

    封装一个获取Ip的函数

    def get_proxy():
        return requests.get("http://127.0.0.1:5010/get/").json()
    

    可以自己测试一下,每次获取到的IP都不同!因此反爬策略做好,这个代理技术不仅仅可以做爬虫,好可以根据自己的需求干其他的

    此时需要修改请求的代码,需要把ip传入

    def request_api(url, params, header,proxy):
        try:
            response = requests.post(url, json=params, headers=header,proxies={"http": "http://{}".format(proxy)})
            if response.status_code == 200:
                return response.json()
            else:
                print(f"请求失败,状态码:{response.status_code}")
                return None
        except requests.exceptions.RequestException as e:
            print(f"请求出现异常:{e}")
            return None
    

    8.保存文件

    保存为两种格式

        path = "河南大学"
        with open(path + ".json", "w", encoding='utf-8') as f:
            json.dump(temp, f, ensure_ascii=False)
        pd.read_json(path + ".json", orient='records').T.to_excel(path + ".xlsx", index=False)
    

    项目的复用性很高,比如说多线程,浏览器伪装和IP代理技术可以很快迁移到其他爬虫任务。

    完整代码

    import time
    import pandas as pd
    import requests
    import json
    from fake_useragent import UserAgent
    import random
    from concurrent.futures import ThreadPoolExecutor  # 导入ThreadPoolExecutor模块
    
    temp = {}
    index = 0
    
    #加载配置文件,请求参数和请求头
    def get_config(page,id):
        # 打开你的json文件
        date = {"local_batch_id":7,"local_province_id":"41","local_type_id":"2","page":1,"school_id":"459","signsafe":"bbddec69cb7fe50f4d9ea21404d72fcf","size":10,"special_group":"","uri":"apidata/api/gkv3/plan/school","year":2023}
        herders = {"Accept":"application/json, text/plain, */*",
                   "User-Agent": UserAgent().random}
        url = "https://api.eol.cn/web/api/?local_batch_id=7&local_province_id=41&local_type_id=1&page={}&school_id={}&size=10&special_group=&uri=apidata/api/gkv3/plan/school&year=2023".format(page,id)
        return date,herders,url
    #获取Ip地址
    def get_proxy():
        return requests.get("http://127.0.0.1:5010/get/").json()
    #发起请求
    def request_api(url, params, header,proxy):
        try:
            response = requests.post(url, json=params, headers=header,proxies={"http": "http://{}".format(proxy)})
            if response.status_code == 200:
                return response.json()
            else:
                print(f"请求失败,状态码:{response.status_code}")
                return None
        except requests.exceptions.RequestException as e:
            print(f"请求出现异常:{e}")
            return None
    
    
    #发起请求,需要发两次
    def spider(i):
        #读取参数
        date, herders,url= get_config(1,i)
        proxy = get_proxy().get("proxy")
        result = request_api(url,date,herders,proxy)['data']
        result_numFound = result['numFound']
        result_item = result['item']
        page_list = [i + 1 for i in range(int(result_numFound / 10) + 1)]
        for page in page_list:
            result_page = date, herders,url= get_config(page,i)
            result = request_api(url, date, herders,proxy)['data']['item']
            time.sleep(random.uniform(1,3))
            get_data(temp,result)
        index = 0
    #数据提取
    def get_data(data,result):
        global index
        for i in range(len(result)):
            data1 = {}
            data1['name'] = result[i]['name']
            data1['province_name'] = result[i]['province_name']
            data1['num'] = result[i]['num']
            data1['spname'] = result[i]['spname']
            data1['spcode'] = result[i]['spcode']
            data1['length'] = result[i]['length']
            data1['level2_name'] = result[i]['level2_name']
            data1['local_batch_name'] = result[i]['local_batch_name']
            data1['local_type_name'] = result[i]['local_type_name']
            data[index] = data1
            print(data1)
            index += 1
    
    #多线程调用爬虫
    def main():
    
        max_workers = 10
        t = []
        t1 = time.time()
        threadPool = ThreadPoolExecutor(max_workers)  # 创建最大线程数为
    
        for i in range(32,34):  # 循环向线程池中提交task任务
            future = threadPool.submit(spider, i)
            t.append(future)
        # 若不需要获取返回值,则可不需要下面两行代码
        for i in t:
            print(i.result())  # 获取每个任务的返回值,result()会阻塞主线程
    
        threadPool.shutdown()  # 阻塞主线程,所有任务执行完后关闭线程池
    
        path = "河南大学"
        with open(path + ".json", "w", encoding='utf-8') as f:
            json.dump(temp, f, ensure_ascii=False)
        pd.read_json(path + ".json", orient='records').T.to_excel(path + ".xlsx", index=False)
        print(time.time() - t1)
    if __name__ == '__main__':
        main()
    
    

    作者:dxw-1997

    物联沃分享整理
    物联沃-IOTWORD物联网 » 如何获取掌上高考(招生计划)信息

    发表回复