爬虫(2) - 网页解析 | 青训营

80 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的第3天

本文章续接上一章juejin.cn/post/713509… ,就爬虫系统的解析部分进行讲解。爬虫系统主要用来爬取数据、解析并存储,该模块较为独立,可以扩展到不同的计算框架下进行调度使用。

首先介绍几个比较有用的工具。

HtmlCleaner

HtmlCleaner是一个开源的Java语言的Html文档解析器。HtmlCleaner能够重新整理HTML文档的每个元素并生成结构良好(Well-Formed)的 HTML 文档。默认它遵循的规则是类似于大部份web浏览器为创文档对象模型所使用的规则。然而,用户可以提供自定义tag和规则组来进行过滤和匹配。其中比较常用的应该是evaluateXPath函数,根据XPath寻找相应的值。

XPath概览

XPath 的选择功能十分强大,它提供了非常简洁明了的路径选择表达式,另外它还提供了超过 100 个内建函数用于字符串、数值、时间的匹配以及节点、序列的处理等等,几乎所有我们想要定位的节点都可以用XPath来选择。

具体来说,XPath为需要读取的目录,可以是 /body/table[2]/tbody/tr/td[2]/table[4]/tbody/tr/td/table/tbody

代码实现

使用evaluateXPath获取网页中某种属性的方法如下。

public static String getAttrByXpath(TagNode tagNode, String attr, String xpath) {
    try {
        Object[] objs = tagNode.evaluateXPath(xpath);
        if (objs != null && objs.length > 0) {
            TagNode node = (TagNode) objs[0];
            return node.getAttributeByName(attr);
        }
    } catch (XPatherException e) {
        e.printStackTrace();
    }
    return null;
}

此外,还可以根据当前网页的基本情况,定义针对某一个属性的个性化的解析函数。下图为获取京东商品基础属性的代码。

public static JSONObject getParams(TagNode tagNode, String xpath, String paramTitleXpath, String paramValueXpath) {
    Object[] objs;
    JSONObject paramJSONObj = new JSONObject();
    try {
        // 获取规格参数中所有的[规格类目]
        objs = tagNode.evaluateXPath(xpath);
        if (objs != null && objs.length > 0) {
            // 遍历每一个[规格类目] div,其中每一个都包含[左侧大标题 + 右侧细节信息]
            for (Object obj : objs) {
                TagNode paramNode = (TagNode) obj;
                // 左侧大的标题
                objs = paramNode.evaluateXPath(paramTitleXpath);
                String paramTitle = null;
                if (objs != null && objs.length > 0) {
                    TagNode paramTitleNode = (TagNode) objs[0];
                    paramTitle = paramTitleNode.getText().toString();
                }
                // 右侧细节信息
                objs = paramNode.evaluateXPath(paramValueXpath);
                JSONObject dlJsonObject = null;
                if (objs != null && objs.length > 0) {
                    dlJsonObject = new JSONObject();
                    TagNode dlNode = (TagNode) objs[0];
                    List<TagNode> childTagList = dlNode.getChildTagList();
                    for (int i = 0; i < childTagList.size()-2; i = i + 2) {
                        TagNode childTagTitle = childTagList.get(i);    // i = 0
                        TagNode chileTagValue = childTagList.get(i + 1); // 1
                        if (chileTagValue.getAttributeByName("class") != null) {
                            i++;
                            chileTagValue = childTagList.get(i + 1);    // 3
                        }
                        dlJsonObject.put(childTagTitle.getText().toString().trim(), chileTagValue.getText().toString().trim());
                    }
                }
                paramJSONObj.put(paramTitle, dlJsonObject);
            }
        }
        return paramJSONObj;
    } catch (XPatherException e) {
        e.printStackTrace();
    }
    return null;
}

对于某些特殊的属性,例如:商品价格,商品评论数,网页将其封装在函数中,可以通过观察html内容的规律,获得其存放的地址。例如京东的商品价格存放在https://p.3.cn/prices/mgets?pduid=1504781656858214892980&skuIds=J_" + id,则其解析方法为:

String priceUrl = "https://p.3.cn/prices/mgets?pduid=1504781656858214892980&skuIds=J_" + id;
String priceJson = HttpUtil.getHttpContent(priceUrl);
if (priceJson != null) {
    if (priceJson.contains("error")) {
        logger.info("价格url已经不可用,请及时更换pduid--->" + priceJson);
    } else {
        JSONArray priceJsonArray = new JSONArray(priceJson);
        JSONObject priceJsonObj = priceJsonArray.getJSONObject(0);
        String priceStr = priceJsonObj.getString("p").trim();
        Float price = Float.valueOf(priceStr);
        page.setPrice(price);
    }
}

参考

zhuanlan.zhihu.com/p/29436838 blog.csdn.net/qq_41943867… www.cnblogs.com/xiehaoyu/p/… blog.51cto.com/xpleaf/2093…