Requests爬取chinadaily海量新闻数据

今天收到一个需求,需要爬取chinadaily网站上查询关键字是HK 和Hong Kong的所有新闻数据用于做NLP。需求字段包括新闻标题,发布时间,新闻内容chinadaily官网
刚开始感觉很简单,不就是个ajax请求json格式吗,但是实际操作时候没那么简单,因为一页返回的十条数据不全都是新闻,而且新闻详情页里面,有的没有发布时间,有的没有标题,有的没有内容,所以不能靠 jsonpath提取,因为你不确定究竟是哪条新闻没有对应的字段,导致数据列表对不上号。
所以换一种思路,提取每个新闻详情的url,再次requests get请求,提取数据,但是当到后面600多页,3000多页时候,网页风格发生了两次调整,包括翻页url变化,详情页标题变化,发布时间变化,内容div变化等。这就是我们不得不多次更改我们的提取策略了,经过几次调整之后,代码如下,
爬取:

import requests
import jsonpath
from lxml import etree
import json

headers = {
    
    
	# 此处的cookie为翻页时候提取的cookie
    "Cookie": "wdcid=2c1008ae99960cdf; UM_distinctid=17cdb56878c89c-0200b6372d3363-a7d173c-186a00-17cdb56878d9b7; __asc=f667c92517cdb5688ae3ad4667f; __auc=f667c92517cdb5688ae3ad4667f",
    "Host": "newssearch.chinadaily.com.cn",
    "Referer": "http://newssearch.chinadaily.com.cn",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
    "X-Requested-With": "XMLHttpRequest"
}


def get_resp(url):
	"""根据翻页url提取信息,为json格式"""
    response = requests.get(headers=headers, url=url)
    return response.json()


def format_data(data):
    """
    提取url两种方案,因为目前发现根据网页风格url至少存在两种提取方案
    :param data:
    :return:
    """
    # 方案1
    url_list = jsonpath.jsonpath(data, "$..shareUrl")
   
    # 方案2
    url_list_1 = jsonpath.jsonpath(data, "$..url")

    i = 0
    for url in url_list:
        if url is None: # 如果第一种方案提取不到
            url_list[i] = url_list_1[i] # 用第二种方案提取到的url替换第一种
        i += 1

    print(url_list)
    return url_list


def save_data(title, pub_time, content, f):
    """
    :param title:  标题
    :param pub_time:  发布时间
    :param content:  发布内容
    :param f:  文件句柄
    :param url:  文章地址
    :return:
    """
    data_dic = {
    
    }
    data_dic["title"] = title
    data_dic["pub_time"] = pub_time
    data_dic["content"] = content
    print(data_dic)
    f.write(json.dumps(data_dic, ensure_ascii=False) + '\n')


def get_url_per_detail(url_list, f):
    """
    获取每页具体详细内容
    :param url_list:
    :return:
    """
    headers = {
    
    
        "referer": "http://newssearch.chinadaily.com.cn/",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "Windows",
        "sec-fetch-dest": "document",
        "sec-fetch-mode": "navigate",
        "sec-fetch-site": "cross-site",
        "sec-fetch-user": "?1",
        "upgrade-insecure-requests": "1",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36",
        # 此处的cookie为具体请求详情页时候提取的cookie
        "cookie": "wdcid=2c1008ae99960cdf; UM_distinctid=17cdb56878c89c-0200b6372d3363-a7d173c-186a00-17cdb56878d9b7; __auc=f667c92517cdb5688ae3ad4667f; wdses=247a72d46467ad42; U_COOKIE_ID=3b41143ce7a6457f1215daf72548dd40; _ga=GA1.3.1878765827.1635773226; _gid=GA1.3.221562497.1635773226; pt_s_3bfec6ad=vt=1635774957349&cad=; pt_s_747cedb1=vt=1635775097596&cad=; pt_747cedb1=uid=SNll4C41NeyncGhS5-bxuw&nid=0&vid=kwH8z7J3HdhFjuHq2QXdNA&vn=2&pvn=3&sact=1635775106946&to_flag=0&pl=FsrbI2FDvRRKNCdlB12cAA*pt*1635775072479; pt_3bfec6ad=uid=NMM5VwPuB9-chzPwYDVYNg&nid=0&vid=NARFb1eDW/2kCbV3kFI5BQ&vn=3&pvn=1&sact=1635775367977&to_flag=1&pl=aaYpoHDZ8hBm2HkBDOWEGw*pt*1635774957349; wdlast=1635776504; CNZZDATA3089622=cnzz_eid%3D938946984-1635764865-null%26ntime%3D1635767813"
    }

    for url in url_list:
        try:
            resp = requests.get(headers=headers, url=url)
            # print(resp.text)
            e = etree.HTML(resp.text)
            # 多规则匹配标题,目前发现两种风格
            title_list = e.xpath("//h1/text() | //h2/text()")
            # 多规则匹配出版时间,目前发现四种风格
            pub_time_list = e.xpath(
                '//div[@class="info"]/span[1]/text() |  //div[@class="content"]/div[1]/p/text() | //div[@class="articl"]//h5/text() | //div[@id="Title_e"]/h6/text()')
             # 多规则匹配内容,目前发现两种风格
            content_list = e.xpath('//div[@id="Content"]/p/text() | //div[@id="Content"]/p[position()<last()]/text()')
            # 只要有一个字段为空,我们就舍弃这条新闻
            if not pub_time_list or not content_list or not title_list:
                continue
            title = title_list[0]
            pub_time = pub_time_list[0].strip().rsplit(": ", 1)[-1]

            content = ""
            for sentence in content_list:
                content += sentence.strip()
            print(url)
            save_data(title=title, pub_time=pub_time, content=content, f=f)

        except Exception as e:
            continue


if __name__ == '__main__':
    while True:
        name = input("请输入查询关键字 \n[Hong kong | HK ]:").strip()
        if not name:
            print("不能为空")
            continue

        if name not in ["Hong kong", "HK"]:
            print("请选择合提供的名称")
            continue
        if name == "Hong kong":
            name = "Hong+kong"
        break

    with open(f"{name}.json", mode="wt", encoding="utf-8") as f:

        i = 1
        for page in range(10000000000000000000):
            print(f"当前正在下载第{i}页......................")
            url = f"http://newssearch.chinadaily.com.cn/rest/en/search?keywords={name}&sort=dp&page={page}&curType=story&type=&channel=&source="

            # 获取分页内容,为json格式
            resp = get_resp(url=url)

            # 是否最后一页判断
            if resp.get("code") == 400:
                break

            # 获取每页的十条url列表
            url_list = format_data(resp)
            

            # 处理每一页具体内容
            get_url_per_detail(url_list, f)

            i += 1

结果展示:
在这里插入图片描述

读取数据:
默认会在当前目录下创建一个 爬取关键字.json的文件,所有数据都以json格式保存为一行一行,目的是方便提取。
拿HK为例,代码如下:

import json

i = 0
with open('HK.json', mode="rt", encoding='utf-8') as f:
    for line in f:
        i += 1
        # 将每一行的json转换成Python字典对象
        dict_data = json.loads(line)
        print(dict_data)
    print(f"一共有{i}行数据")

扩展: 由于数据量太大,应该写成多线程方式爬取,或者改进用Scrapy框架实现分布式爬取,可以大大减小等待时间。

猜你喜欢

转载自blog.csdn.net/m0_46090675/article/details/121140000