上一篇《人民日报》的爬虫文章发布之后,收到了很不错的反馈,文中的爬虫代码也确实帮助到了很多人,我很开心。
跟读者们交流过程中,我也发现了一些比较共性的需求,就是 根据关键词筛选
新闻文章。
最初我的想法是,在爬取到全部文章数据的基础上,遍历文件夹,然后将正文中包含关键词的文章筛选出来。
如果你已经下载到了完整的新闻数据,那用这种方法无疑是最方便快捷的。但是如果没有的话,需要先爬取全部数据,再从中筛选符合条件的数据,无疑是有点浪费时间。
本篇文章,我将介绍两种方法,一种,是从现有数据中根据关键词筛选,另一种,是利用人民网的搜索功能,爬取关键词的搜索结果。
1. 爬取关键词搜索结果
最近有读者跟我请教问题,我才发现,人民网是有搜索功能的 (http://search.people.cn)。
所以只需要根据关键词检索,然后将搜索结果爬取下来即可。
1.1 分析网页
这里我简单教一下大家分析网页的一般思路。
1.1.1 分析网页主要看什么
- 我们能获取到哪些数据。
- 服务器以什么形式发送数据
- 如何获取全部数据(如何翻页)
1.1.2 如何使用浏览器的开发者工具
具体操作也很简单,按 F12 打开 开发者工具
,切换到 Network
,然后刷新网页,可以看到列表中有很多请求。
其中有图片,js 代码, css 样式,html 源码等等等等各种各样的请求。
点击对应的请求项之后,你可以在 Preview
或者 Response
里预览该请求的数据内容,看是不是包含你需要的数据。
当然,你可以逐条去检查,也可以用顶部的过滤器去筛选请求类型(一般情况下,我们需要的数据,在 XHR
和 Doc
里即可找到。)
找到对应的请求之后,可以切换到 headers
查看该请求的请求头信息。
如图所示,主要关注 4 个地方。
- Request URL:也就是请求的链接。爬虫请求的
url
填啥是要看这里,不要只知道去浏览器的地址栏里复制网址。 - Request Method:请求方法,有
GET
和POST
两种,爬虫代码里用requests.get()
还是requests.post()
要与这里保持一致,否则可能无法正确获取数据。 - Request Headers:请求头,服务器会根据这个来判断是谁在访问网站,一般情况下,你需要设置爬虫请求头中的
User-Agent
(有的网站可能需要判断Accept
,Cookie
,Referer
,Host
等,根据具体情况设置),来将爬虫伪装成正常的浏览器用户,防止被反爬机制拦截。 - Request Payload:请求参数,服务器会根据这些参数,来确定返回哪些数据给你,比如说,页码,关键词,等等,找到这些参数的规律,就可以通过构造这些参数,来直接向服务器获取数据了。
1.1.3 服务器返回的数据有哪些形式
一般情况下,有html
和 json
两种格式,接下来我简单教大家一下如何判断。
HTML 格式
一般情况下,是出现在筛选条件中的 Doc
类型里,也非常容易分辨,就是 Response
里面查看,全篇都是 </> 这种标签的。
如果确定 html
源码中包含你需要的数据的话(这么说,是因为有些情况下数据是通过 js 代码动态加载进去的,直接解析源码是找不到数据的)
可以在 Elements
中,通过左上角的箭头按钮,来方便快捷地在网页中定位数据所在的标签(具体就不细讲了,自己试一下就懂了)。
大部分人入门学习爬虫,也都是从解析 html
开始的,应该比较熟悉。解析的方法也很多,如 正则表达式
,BeautifulSoup
, xpath
等等。
Json 格式
前面也说了,有些情况下,数据并不是放在 html 页面中直接返回的,而是通过其他的数据接口,动态的请求加载进去的。这样就导致的有些同学刚开始学爬虫的时候,明明在网页上分析的时候,标签路径都没问题,但是代码请求时死活找不到标签的情况。
这种动态加载数据的机制叫 Ajax
,感兴趣的可以去自行搜索了解。
Ajax
请求一般都是在请求类型中的 XHR
中,数据内容一般以 json
格式展示。(有些同学不知道怎么判断一个请求是不是 ajax,数据是不是 json,怎么办?这里说一个简单的判断方法,在 Preview
里看它是不是类似于下图的形式,大括号,键值对 {“xxx”: “xxx”},以及可以展开合上的小三角形)
这类请求返回的数据是 json
格式,可以直接使用 python
中的 json
库来解析,非常方便。
以上给大家简单讲解了一下,如何分析网页,如何抓包的方法,希望对大家有帮助。
言归正传,通过上面介绍的方法,我们可以很容易知道,人民网搜索结果的数据是通过 Ajax 形式发送的。
请求方法是 POST,请求链接,请求头,和请求参数都可以在 Headers
里查看到。
在参数里,我们可以看到 key
应该就是我们搜索的关键词,page
是页码,sortType
是搜索结果排序方式,等等等等,知道这些规律,这样我们就可以自行构造请求了。
1.2 试探反爬机制
一般网站为了防止攻击,都会或多或少设置一些反爬机制。这里给大家简单介绍一些常见的反爬机制,以及应对办法。
1.2.1 User-Agent
服务器会根据请求头的 User-Agent
字段来判断用户是通过什么来访问的,如:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36
这里包含了浏览器,电脑系统的一些基本信息。如果你的 python 爬虫代码没有设置这个字段值的话,会默认是 python
,这样服务器就可以大概判断出来这个请求是爬虫发起的,然后选择是否拦截。
应对办法也比较容易,就是用浏览器访问时候,把请求头里的 User-Agent
值复制下来,设置到代码里即可。
1.2.2 Referer
有些网站的资源是添加了防盗链的,也就是说,服务器处理请求的时候,是会去判断 Referer
的值的,只有在指定的站点发起请求,服务器才会允许返回数据(这样可以防止资源被盗用在其他网站使用)。
应对方法也简单,复制浏览器访问时,请求头里的 Referer
值即可。
1.2.3 Cookie
有的网站,可能有的数据需要登录账号才能访问,这里就用到了 Cookie
值。
不设置 Cookie
, 设置未登录时候访问的 Cookie
,设置登录账号以后的 Cookie
,可能返回的数据结果都不一样。
应对方法因网站而异,如果不设置 Cookie
也可以访问,那就不用管;如果需要设置才能访问,那就根据情况(是否要登录,是否要会员等等)复制浏览器请求头中的 Cookie
值来设置。
1.2.4 JS 参数加密
请求的参数里,可能会有一些类似于乱码的参数,你不知道它是干什么的但是它又很重要,也不是时间戳,不填或者随便填都会请求失败。
这种情况就比较难搞了,这是 js
算法加密过的参数,要想自行构造,需要模拟整个参数加密的算法。
不过由于这个加密过程是前端完成的,所以加密算法的 js 代码是可以完全获取的到的,懂一些前端知识的,或者 Js 逆向的话,可以试着破解一下。
我个人是不建议这么做的,一是破解麻烦,二是可能会违法。
换个方法,用 selenium
或者 ``pyppeteer` 自动化去爬,它不香嘛。
1.2.5 爬取频率限制
如果长时间高频率地爬取数据的话,对网站服务器的压力是很大的,而且正常人访问也不可能做到这么高强度的访问(比如一秒访问十几次网站),一看就是爬虫干的。所以服务器一般会设置一个访问频率阈值,比如一分钟内如果发起超过 300 次请求,就视为爬虫,对其 IP 进行访问限制。
应对的话,我建议,如果不是特别赶时间的话,可以设置一个延迟函数,每爬取一次数据,随机睡眠几秒钟,使得访问频率降低到阈值以下,降低对服务器访问的压力,也减少封IP的几率。
1.2.6 其他
还有一些不太常见的,但是也比较有意思的反爬机制,给大家举几个例子。
- 字体反爬,就是网站对一些关键数据使用了特殊的字体,只有用对应的字体文件去解析,才能正确显示,否则源码里只能看到一串乱码。
- 反调试,就是网站通过屏蔽浏览器开发者工具,屏蔽
selenium
等常用的爬虫工具,来达到一定的反爬效果。 - 蜜罐反爬,就是检测到你是爬虫的时候,不会直接拒绝访问,而是给你返回一些假数据。爬虫者可能爬完了才发现是假数据,而且甚至不知道是什么时候开始被替换成假数据的。
以上是常见的一些反爬机制,希望对大家有帮助。
经过测试,人民网的反爬机制并不是特别严格,参数设置对了的话,基本上爬取不会受到什么限制。
不过如果是比较大量的数据爬取时,最好设置好爬取延时
以及 断点续爬
功能。
1.3 完善代码
首先导入需要的库。
每个库在本爬虫代码中的用处在注释中已经标注。
import requests # 发起网络请求
from bs4 import BeautifulSoup # 解析HTML文本
import pandas as pd # 处理数据
import os
import time # 处理时间戳
import json # 用来解析json文本
发起网络请求函数 fetchUrl
函数用途及三个参数的含义,已在代码注释中标注,返回值为 json
类型的数据
'''
用于发起网络请求
url : Request Url
kw : Keyword
page: Page number
'''
def fetchUrl(url, kw, page):# 请求头headers={"Accept": "application/json, text/plain, */*","Content-Type": "application/json;charset=UTF-8","User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36",}# 请求参数payloads = {"endTime": 0,"hasContent": True,"hasTitle": True,"isFuzzy": True,"key": kw,"limit": 10,"page": page,"sortType": 2,"startTime": 0,"type": 0,}# 发起 post 请求r = requests.post(url, headers=headers, data=json.dumps(payloads))return r.json()
数据解析函数 parseJson
解析 json
对象,然后将解析到的数据包装成数组返回
def parseJson(jsonObj):#解析数据records = jsonObj["data"]["records"];for item in records:# 这里示例解析了几条,其他数据项如末尾所示,有需要自行解析pid = item["id"]originalName = item["originalName"]belongsName = item["belongsName"]content = BeautifulSoup(item["content"], "html.parser").textdisplayTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(item["displayTime"]/1000))subtitle = item["subtitle"]title = BeautifulSoup(item["title"], "html.parser").texturl = item["url"]yield [[pid, title, subtitle, displayTime, originalName, belongsName, content, url]]
数据保存函数 saveFile
'''
用于将数据保存成 csv 格式的文件(以追加的模式)
path : 保存的路径,若文件夹不存在,则自动创建
filename: 保存的文件名
data : 保存的数据内容
'''
def saveFile(path, filename, data):# 如果路径不存在,就创建路径if not os.path.exists(path):os.makedirs(path)# 保存数据dataframe = pd.DataFrame(data)dataframe.to_csv(path + filename + ".csv", encoding='utf_8_sig', mode='a', index=False, sep=',', header=False )
主函数
if __name__ == "__main__":# 起始页,终止页,关键词设置start = 1end = 3kw = "春节"# 保存表头行headline = [["文章id", "标题", "副标题", "发表时间", "来源", "版面", "摘要", "链接"]]saveFile("./data/", kw, headline)#爬取数据for page in range(start, end + 1):url = "http://search.people.cn/api-search/elasticSearch/search"html = fetchUrl(url, kw, page)for data in parseJson(html):saveFile("./data/", kw, data)print("第{}页爬取完成".format(page))# 爬虫完成提示信息print("爬虫执行完毕!数据已保存至以下路径中,请查看!")print(os.getcwd(), "\\data")
以上为本爬虫的全部代码,大家可以在此基础上进行修改来使用,仅供学习交流,切勿用于违法用途。
注:这里没有写正文爬取的代码,一是人民网文章正文爬取的函数在上一篇文章中已经写了,大家有需要的话可以自行整合代码;二是,爬取正文的话会引入一些其他的问题,比如链接失效,文章来源于不同网站,解析方式不同等问题,说来话长,本文主要以思路讲解为主。
1.4 成果展示
1.4.1 程序运行效果
1.4.2 爬到的数据展示
2. 利用现有数据筛选
如果你已经提前下载到了全部的新闻文章数据,那用这种方法无疑是最方便的,省去了漫长的爬取数据的过程,也省得跟反爬机制斗智斗勇。
2.1 数据来源
下载地址:https://github.com/caspiankexin/people-daily-crawler-date
以上是一个读者朋友爬取的人民日报新闻数据,包含从 19 年起至今的数据,每月一更新,应该可以满足很大一部分人对数据的需求了。
此外我还有之前爬的 18 年全年的数据,如果有需要的朋友,可以私聊找我要。
2.2 检索代码
以如下图所示的目录结构为例。
假设我们有一些关键词,需要检测这些新闻文章中哪篇包含有关键词。
import os# 这里是你文件的根目录
path = "D:\\Newpaper\\2018"# 遍历path路径下的所有文件(包括子文件夹下的文件)
def iterFilename(path):#将os.walk在元素中提取的值,分别放到root(根目录),dirs(目录名),files(文件名)中。for root, dirs, files in os.walk(path):for file in files:# 根目录与文件名组合,形成绝对路径。yield os.path.join(root,file)# 检查文件中是否包含关键词,若包含返回True, 若不包含返回False
def checkKeyword(filename, kwList):with open(filename, "r", encoding="utf-8") as f:content = f.read()for kw in kwList:if kw in content:return True, kwreturn False, ""if __name__ == "__main__":# 关键词数组kwList = ["经济", "贸易"]#遍历文章for file in iterFilename(path):res, kw = checkKeyword(file, kwList)if res:# 如果包含关键词,打印文件名和匹配到的关键词print("文件 ", file," 中包含关键词 ", kw)
2.3 运行结果
运行程序,即可从文件中筛选出包含关键词的文章。
2021年9月9日更新
最近有读者在复现文中的爬虫时,发现有如下报错:
in parseJson records = jsonObj[“data”][“records”];
TypeError:“data”
经过调试检查后,发现这个报错是因为原网站中,关键词搜索的接口更换了,导致数据获取失败。
解决办法:
将主函数部分的 url
替换成新的接口 http://search.people.cn/api-search/front/search
即可,如下所示。
if __name__ == "__main__":# 起始页,终止页,关键词设置start = 1end = 3kw = "春节"# 保存表头行headline = [["文章id", "标题", "副标题", "发表时间", "来源", "版面", "摘要", "链接"]]saveFile("./data/", kw, headline)#爬取数据for page in range(start, end + 1):# url = "http://search.people.cn/api-search/elasticSearch/search"url = "http://search.people.cn/api-search/front/search"html = fetchUrl(url, kw, page)for data in parseJson(html):saveFile("./data/", kw, data)print("第{}页爬取完成".format(page))# 爬虫完成提示信息print("爬虫执行完毕!数据已保存至以下路径中,请查看!")print(os.getcwd(), "\\data")
2021年12月13日更新
最近有读者反馈,程序运行又报错了,可能是网站那个接口又换了。
我查看了一下,确实是这样。
只需要将接口替换为 http://search.people.cn/search-platform/front/search
即可,替换方法同上。
如果文章中有哪里没有讲明白,或者讲解有误的地方,欢迎在评论区批评指正,或者扫描下面的二维码,加我微信,大家一起学习交流,共同进步。