目录
任务
接到一个任务,需要统计标准文献在新闻中被提到的次数。例如:这篇新闻中就提到了《GB 18030-2000》这个标准。现有约10万条标准需要统计,所以我准备使用webmagic编写一个爬虫,统计新闻的个数。
总体思路
使用百度搜索
常见搜索新闻的方式有一下这些:
- 各大搜索引擎都提供的新闻搜索服务,如百度新闻搜索,搜狗新闻搜索等。
- 各大新闻网站提供的站内新闻搜索服务,如新浪站内新闻搜索 。
- 指定新闻站点,使用普通的网页搜索得到新闻。
经过横向比较,我发现:
- 直接使用专门的新闻搜索引擎,如百度新闻搜索,搜狗新闻搜索等。虽然使用方便,但得到的结果数量却远小于其他两种方式。
- 很多新闻网站的站内搜索其实使用的还是各大搜索引擎的新闻搜索服务,如搜狐新闻、腾讯新闻使用的是搜狗新闻搜索。
- 虽存在一些表现很好、独立的站内搜索,但大多数新闻网站的站内搜索服务很难用,对于标准号的搜索,存在搜索不精确,答非所问的问题。
- 新闻网站站内搜索非常独立,若想尽可能多地搜索新闻,那么每一家媒体就需要编写一个特定的爬虫,工作量很大。
- 指定新闻站点使用普通的网页搜索,这种方式虽然搜索出的结果数量不如优秀的站内搜索给出的数量多,但是当指定足够多而全的站点逐个搜索时,结果同样会很好。
综上所述, 我决定:
- 对于站内搜索服务好的新闻网站,使用它的站内搜索服务得到新闻,如新浪的新闻直接使用新浪新闻搜索。
- 其他的新闻网站,使用第三种方式——指定它们与新闻有关的域名使用普通的网页搜索得到新闻。如搜索搜狐的新闻,就指定news.sohu.com、business.sohu.com、society.sohu.com、it.sohu.com等众多搜狐与新闻有关的域名,使用普通的网页搜索得到新闻。
本次做的程序就是使用百度进行网页搜索的,对于使用特定新闻网站站内搜索的方式,本次不做实现。
使用百度搜索某一站点的新闻,例如这样:
是搜索《GB 2312》
在news.qq.com这个站点的新闻。
在搜索时,浏览器发送GET 请求给http://www.baidu.com/s
就会返回搜索结果了,我们需要注意的请求参数是以下这些:
参数名 | 解释 |
---|---|
wd | 搜索关键字 |
si | 待搜索的站点 |
ct | 与si搭配使用,值固定为2097152 |
rn | 每页显示的结果条数,最大为50 |
所以,百度搜索的请求URL就是如下的形式:
www.baidu.com/s?wd=标准号
&si=站点
&ct=2097162&rn=50
筛选搜索结果
通过请求上述的URL,就可以得到搜索结果了,只要对结果页翻页,遍历全部结果。
但百度搜索的结果存在一些不如意的地方:
- 百度有可能会对搜索内容进行分词,这导致搜索出的结果未必是我们想要的,例如下面这种情况
从结果的摘要中可以判断该条结果不符合搜索条件。 - 存在少数情况,其实内容是符合要求的,摘要却没有标注出被搜索的关键字的情况。如以下这样:
这条结果的摘要中被没有我们搜索的关键字,但实际上该页面是存在我们搜索的关键字的:
- 搜索出大量结果,但与关键字全无关系。这在标准号字长较短时常见,处理它们会浪费很多不必要的时间。
- 仅前几条搜索结果符合,但之后存在数十页不符合要求的结果。同样会浪费不必要的时间,非常影响性能。
综合以上几点不足,本程序将运用一下几条规则去筛选新闻:
- 搜索结果逐页检查判断。
- 检查摘要中是否包含被搜索的标准号,若包含就将新闻储存,若不包含,就通过百度快照继续检查该条结果的原文中是否包含被搜索的标准号。(注:由于百度快照会将原文中与关键字有关的内容用b标签标注出来,故使用百度快照可以更加精确地检查判断,效率更高)
- 若搜索结果的第一页前十条都是不必配的新闻,那么将视为本次搜索结果为0,不再检查剩余的搜索结果。
- 若搜索结果的其中一页中,整页都没有匹配的新闻,那么将不再翻页。
新闻URL的转换
在百度中给出的链接都是形如https://www.baidu.com/link?url=xxxx
这样的URL,并非网页原来的URL,要做地址转换将百度的地址转换为网页原始的URL。
使用Charles和Postman这两个工具经过反复观察,我们发现,百度的链接可分为两种情况:
- 链接中仅有url一个参数,形如
https://www.baidu.com/link?url=xxxx
。请求这个地址,百度的服务器会返回302重定向,在Respons的Header中Location会给出链接的URL: - 链接中包含url、wd、eqid三个参数,形如
https://www.baidu.com/link?url=xxxx&wd=&eqid=xxxxx
.请求这个地址,百度会返回这样的内容:可以看到在<noscript>
标签中百度给出了网页的原始URL。
储存新闻
利用百度得到的新闻要存储到数据库中,本程序使用spring-data-jpa
操作数据库。
标准引用的统计工作
进过以上的步骤,我们可以把引用了标准的新闻保存在了数据库中。为统计某一条标准被引用了多少次,需要做两个事情:
- 去重:数据库中引用同一条标准的同一条新闻记录只保留一条
- 计数统计:根据数据库中新闻的引用信息,更新标准的被引用字段。
代码实现
运行流程
本程序的运行流程如下图所示
其中通过百度爬取新闻
和转换新闻地址
是两个爬虫程序。新闻去重
和统计引用次数
是两个数据库操作,仍然使用jpa去实现。
两个爬虫的结构
我使用webmagic框架编写两个爬虫BaiduForEachCrawler
和BaiduURLConverter
,分别实现通过百度爬取新闻
和转换新闻地址
这两个功能。(webmagic基本结构请查看webmagic中文文档)
他们的组织分别如下:
BaiduForEachCrawler
这个类用来从百度搜索爬取新闻。BaiduForEachCrawler
Downloader
BaiduForEachCrawler使用的Downloader为webmagic默认的Downloader。
Scheduler
Scheduler用于管理待爬取的URL。
本次任务中的管理URL有一下一些问题需要解决:
- 约有10万条标准,假设每条标准需要指定从100个站点搜索,那么初始状态下大约将有一千万个URL需要保存和管理。
- 爬取过程中还会产生新的百度快照的URL加入Scheduler中。
- 大量的URL爬取需要花费很长的时间,一次运行处理完全部的URL将不太可取。
- 保存大量的URL会占用大量的内存空间。
为解决上述的问题,设计如下:
- 使用两个Queue保存待爬取的URL,一个保存百度快照的URL,一个保存百度搜索的URL。运行时优先处理百度快照的URL,这样保证待爬取URL的总数量不会越来越大。
- 创建两个文件,分别保存两个Queue中的内容。这样保证在程序关闭并再次启动后可以继续之前的爬取进度运行。
- 分批将URL添加到Scheduler中,处理完一批再添加一批,确保空间占用不会太大。
- 本次程序中待爬取的URL不会出现重复的情况,故Scheduler可以去掉去重的功能,这样可以节省时间和空间。
最后,本程序将webmagic内置的FileCacheScheduler做了一些修改,来实现上述的功能。具体详见源码:FileCachePriorityQueueScheduler
ps:后来我觉得使用Redis来保存URL应该会更好些,当时不了解啊
PageProcessor
爬虫需要处理百度搜索和百度快照两种页面,故分别设计BaiduSearchForEachPageProcessor和BaiduCachePageProcessor两个PageProcessor来处理。两个PageProcessor通过CompositePageProcessor
组织起来。
若对百度快照的内容全文查找关键字,那效率会非常低。为了提高BaiduCachePageProcessor的效率,更加精确地处理,程序会将百度快照页面中<b>
标签的上一级父节点选择出来,统计这些父节点中包含的<b>
标签的数量,最后程序会根据<b>
标签的数量从多到少依次检查这些父节点中是否存在我们搜索的关键字。存在关键字的就是我们要找的匹配的新闻。
Pipeline
pipeline用于持久化爬取的结果。这个爬虫使用webmagic内置的ConsolePipeline将结果输出到控制台,使用NewsSaveIntoMysqlPipeLine将筛选到的新闻保存到mysql数据库。
BaiduURLConverter
将百度链接转换为原始的URL。BaiduURLConverter
Downloader
webmagic内置的Downloader默认会重定向下载重定向后的页面,但我们现在只需要知道重定向的URL并不需要下载其内容,故我将它内置的HttpClientDownloader和HttpUrlRequestConverter做了修改,使其不会重定向,并将重定向的地址写在了响应的Body内。
MyDownloader113行处将重定向地址写入了Body内。
MyHttpRequestConverter:70行处禁止了重定向
Scheduler
待转换的URL数量同样非常多,所以采用同上一个爬虫BaiduForEachCrawler一样的处理方式,使用Queue存放URL,同时保存到文件中以实现点断处理。唯一的不同时,转换地址时只需要一个Queue即可。
PageProcessor
本爬虫的PageProcessor作用就是将Body中重定向URL提取出来。BaiduURLConverterPageProcessor
PipeLine
与前一个爬虫一样,同样是将新闻保存到MySQL中,故使用的PipeLine为同一个。NewsSaveIntoMysqlPipeLine
新闻去重与统计
这两个步骤根本上是对数据库的操作。本程序使用spring-data-jpa
对数据库操作,逻辑在Repository中实现。NewsRepo
新闻去重的思路是按照标准号和新闻的URL进行分组,SELECT出每一小组内数量多于1的新闻,并删除重复新闻只保留id号最小的记录。
SQL语句如下所示:
DELETE FROM std_news WHERE (a100,news_url)
IN (
SELECT a100,news_url FROM
(
SELECT a100,news_url FROM std_news
GROUP BY a100,news_url
HAVING count(*) > 1
) a
) AND id NOT IN
(
SELECT * FROM
(
SELECT min(id) FROM std_news
GROUP BY a100,news_url
HAVING count(*) > 1
) b
)
统计标准的引用次数思路很简单,使用jpa很容易实现。
为运行做一些事情
- 为更好地监控管理程序,本程序支持使用JMX监控。webmagic监控。
- 程序在使用时打包为jar包,在服务器上运行。