今天收到一个需求,需要爬取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框架实现分布式爬取,可以大大减小等待时间。