最近的项目需要访问一些外部的网页,比如百度、必应搜索等,有的只是要求把响应内容的文本提取出来,有的是要求做结构化解析的,比如百度的搜索结果,解析为一个结构化的列表,每个列表项有(标题、概要信息、详细信息的链接)。
开始时感觉是用爬虫去爬网页,然后做解析,但有个问题困扰着:比如要保存百度、必应的搜索结果到数据库,肯定只希望定义一个类来对应搜索结果的一条记录,然后写一套保存到数据库的代码。看了一些爬虫框架对于内容解析部分其实都只能自己写代码来解析网页,这样的话,如果要接搜狗就写一套解析网页的逻辑、接 360 也得写一套。。。。每接一个就得写一套。。。这不是我需要的生活。。。
怎么减少这些匹配代码?
后面看到 webmagic 的文档提到其基于注解实现的 Object/Extraction Mapping 功能,在类的字段上定义一个注解,表示这个字段用什么样的方式抽取,这样在爬虫有关的代码里就不会出现这些解析网页的逻辑。
Object Extract Mapping
如果我只需要解析百度的搜索结果,这是很好的方式,但我还要解析必应、搜狗等更多的搜索结果时就不行了。因此需要换一种方式来实现:把抽取方式的定义放到代码外面来。
我就实现了一个基于 XML 的 Object-Extrac-Mapping (OEM) 小工具。
下面是 OEM 定义的举例:解析百度、必应搜索结果的。
<oems>
<oem>
<name>baidu</name>
<mappings>
<mapping>
<selectorType>xpath</selectorType>
<selector><![CDATA[//div[@id='container']/div[@id='content_left']/div[@class='result']]]></selector>
<resultType>list</resultType>
<pojoClass>net.coderbee.app.entity.SearchResult</pojoClass>
<props>
<prop>
<selectorType>xpath</selectorType>
<selector><![CDATA[h3/a/text()]]></selector>
<propName>title</propName><!-- 标题 -->
</prop>
<prop>
<selectorType>xpath</selectorType>
<selector><![CDATA[h3/a/@href]]></selector>
<propName>detailUrl</propName><!-- 目标链接的地址 -->
</prop>
<prop>
<selectorType>xpath</selectorType>
<selector><![CDATA[div/text_content()]]></selector>
<propName>outline</propName><!-- 概要 -->
</prop>
</props>
</mapping>
</mappings>
</oem>
<oem>
<name>bing</name>
<mappings>
<mapping sub="false">
<selectorType>xpath</selectorType>
<selector><![CDATA[//ol[@id='b_results']/li]]></selector>
<resultType>list</resultType>
<pojoClass>net.coderbee.app.entity.SearchResult</pojoClass>
<props>
<prop>
<selectorType>xpath</selectorType>
<selector><![CDATA[//h2/a/text()]]></selector>
<propName>title</propName>
</prop>
<prop>
<selectorType>xpath</selectorType>
<selector><![CDATA[//h2/a/@href]]></selector>
<propName>detailUrl</propName>
</prop>
<prop>
<selectorType>xpath</selectorType>
<selector><![CDATA[div[2]/allText()]]></selector>
<propName>outline</propName>
</prop>
</props>
</mapping>
</mappings>
</oem>
<!-- 其他 oem 定义 -->
</oems>
上面的功能只是针对网页结构做提取,对于提取到文本值没法做定制化的处理,比如提取到 “日期:2016-08-30″,其实是只想提取 “2016-08-30″。可以在 <prop>
标签里加一个子元素 <script></script>
允许定义一小段脚本对提取到的文本值做加工,加工后的值再设置到 POJO 的属性上。
网页 API
当有目标地获取网页上的结构化信息时,就不应该以爬虫的思维去考虑怎么获取,而应该是当作 API 来处理。
比如百度上的搜索,它其实是一个查询接口,接收一个要查询的关键字参数、一个第几页搜索结果的分页参数。
对于其他的搜索引擎也是类似,只不过可能结果的分页方式不一样,有的是按页的下标,有的是按记录的下标。这些其实都可以配置到 XML 里。有了这些配置,调用这些网页的代码就可以统一起来,只写一次,然后结合 OEM 来解析结果。
下面是个配置的举例:
<apis>
<api>
<name>baidu</name>
<baseUrl><![CDATA[https://www.baidu.com/s]]></baseUrl>
<keywordParamName>wd</keywordParamName>
<pagingParamName>pn</pagingParamName>
<pagingType>recordIndex</pagingType>
<pageSize>10</pageSize>
<recordStartFrom>0</recordStartFrom>
</api>
<api>
<name>bing</name>
<baseUrl><![CDATA[http://cn.bing.com]]></baseUrl>
<keywordParamName>q</keywordParamName>
<pagingParamName>first</pagingParamName>
<pagingType>recordIndex</pagingType>
<pageSize>10</pageSize>
<recordStartFrom>1</recordStartFrom>
</api>
</apis>
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。