上一篇《人民日报》的爬虫文章发布之后,收到了很不错的反馈,文中的爬虫代码也确实帮助到了很多人,我很开心。

跟读者们交流过程中,我也发现了一些比较共性的需求,就是 根据关键词筛选 新闻文章。

最初我的想法是,在爬取到全部文章数据的基础上,遍历文件夹,然后将正文中包含关键词的文章筛选出来。

如果你已经下载到了完整的新闻数据,那用这种方法无疑是最方便快捷的。但是如果没有的话,需要先爬取全部数据,再从中筛选符合条件的数据,无疑是有点浪费时间。

本篇文章,我将介绍两种方法,一种,是从现有数据中根据关键词筛选,另一种,是利用人民网的搜索功能,爬取关键词的搜索结果。

1. 爬取关键词搜索结果

最近有读者跟我请教问题,我才发现,人民网是有搜索功能的 (http://search.people.cn)。

所以只需要根据关键词检索,然后将搜索结果爬取下来即可。

1.1 分析网页

这里我简单教一下大家分析网页的一般思路。

1.1.1 分析网页主要看什么

  • 我们能获取到哪些数据。
  • 服务器以什么形式发送数据
  • 如何获取全部数据(如何翻页)

1.1.2 如何使用浏览器的开发者工具

具体操作也很简单,按 F12 打开 开发者工具 ,切换到 Network ,然后刷新网页,可以看到列表中有很多请求。

其中有图片,js 代码, css 样式,html 源码等等等等各种各样的请求。

点击对应的请求项之后,你可以在 Preview 或者 Response 里预览该请求的数据内容,看是不是包含你需要的数据。

当然,你可以逐条去检查,也可以用顶部的过滤器去筛选请求类型(一般情况下,我们需要的数据,在 XHRDoc 里即可找到。)

找到对应的请求之后,可以切换到 headers 查看该请求的请求头信息。

如图所示,主要关注 4 个地方。

  1. Request URL:也就是请求的链接。爬虫请求的 url 填啥是要看这里,不要只知道去浏览器的地址栏里复制网址。
  2. Request Method:请求方法,有 GETPOST 两种,爬虫代码里用 requests.get() 还是 requests.post() 要与这里保持一致,否则可能无法正确获取数据。
  3. Request Headers:请求头,服务器会根据这个来判断是谁在访问网站,一般情况下,你需要设置爬虫请求头中的 User-Agent (有的网站可能需要判断 AcceptCookieRefererHost 等,根据具体情况设置),来将爬虫伪装成正常的浏览器用户,防止被反爬机制拦截。
  4. Request Payload:请求参数,服务器会根据这些参数,来确定返回哪些数据给你,比如说,页码,关键词,等等,找到这些参数的规律,就可以通过构造这些参数,来直接向服务器获取数据了。

1.1.3 服务器返回的数据有哪些形式

一般情况下,有htmljson 两种格式,接下来我简单教大家一下如何判断。

HTML 格式

一般情况下,是出现在筛选条件中的 Doc 类型里,也非常容易分辨,就是 Response 里面查看,全篇都是 </> 这种标签的。

如果确定 html 源码中包含你需要的数据的话(这么说,是因为有些情况下数据是通过 js 代码动态加载进去的,直接解析源码是找不到数据的)

可以在 Elements 中,通过左上角的箭头按钮,来方便快捷地在网页中定位数据所在的标签(具体就不细讲了,自己试一下就懂了)。

大部分人入门学习爬虫,也都是从解析 html 开始的,应该比较熟悉。解析的方法也很多,如 正则表达式BeautifulSoupxpath 等等。

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").text
        displayTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(item["displayTime"]/1000))
        subtitle = item["subtitle"]
        title = BeautifulSoup(item["title"], "html.parser").text
        url = 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 = 1
    end = 3
    kw = "春节"
    
    # 保存表头行
    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 爬到的数据展示

image-20210206201915970

2. 利用现有数据筛选

如果你已经提前下载到了全部的新闻文章数据,那用这种方法无疑是最方便的,省去了漫长的爬取数据的过程,也省得跟反爬机制斗智斗勇。

2.1 数据来源

下载地址:https://github.com/caspiankexin/people-daily-crawler-date

以上是一个读者朋友爬取的人民日报新闻数据,包含从 19 年起至今的数据,每月一更新,应该可以满足很大一部分人对数据的需求了。

此外我还有之前爬的 18 年全年的数据,如果有需要的朋友,可以私聊找我要。

2.2 检索代码

以如下图所示的目录结构为例。

image-20210206202425501

假设我们有一些关键词,需要检测这些新闻文章中哪篇包含有关键词。

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, kw
    return False, ""
            
if __name__ == "__main__":
    # 关键词数组
    kwList = ["经济", "贸易"]
    #遍历文章
    for file in iterFilename(path):
        res, kw = checkKeyword(file, kwList)
        if res:
            # 如果包含关键词,打印文件名和匹配到的关键词
            print("文件 ", file," 中包含关键词 ", kw)

2.3 运行结果

运行程序,即可从文件中筛选出包含关键词的文章。


如果文章中有哪里没有讲明白,或者讲解有误的地方,欢迎在评论区批评指正,或者扫描下面的二维码,加我微信,大家一起学习交流,共同进步。

最后修改:2021 年 05 月 29 日 02 : 29 PM
如果觉得我的文章对你有用,请随意赞赏