1. 引言

现代网页往往其HTML只有基本结构,而数据是通过AJAX或其他方法获取后填充,这样的模式对爬虫有一定阻碍,但是熟练以后获取并不困难,本文以爬取天猫评论为例简单讲讲动态获取以及自定义Pipeline进行数据清洗的过程。

2. 爬取商品信息

我们访问s.taobao.com/search?q=你搜索的关键字 时可以很容易的获取到搜索结果页面,不难发现淘宝把搜索结果的信息嵌入到了该获取结果的head标签之中,可以很容易的通过xpath将该信息抽取出来并整理成一个Json,你可以发现其中中文部分是由Unicode编码编写的,你可以自己写一个convert函数去解决这个问题。

这里我也提供一个简单的convert函数,可以仅转换文本中的Unicode编码:

public class Unicode2utf8Utils {
public static String convert(String unicodeString) {
StringBuilder stringBuilder = new StringBuilder();
int i = -1;
int pos = 0;
while ((i = unicodeString.indexOf("\\u", pos)) != -1) {
stringBuilder.append(unicodeString.substring(pos, i));
if (i + 5 < unicodeString.length()) {
pos = i + 6;
stringBuilder.append((char) Integer.parseInt(unicodeString.substring(i + 2, i + 6), 16));
}
}
return stringBuilder.toString();
}
}

这里由于淘宝该数据是用Json格式发送的,可以很容易的用JsonPathSelector这个工具去获取想要的字符,当然,使用前需要对数据进行一些清理,让它是一个方便识别的Json文本。(这些清理是根据具体实践中爬到的内容进行分析得到的,你需要自己实践看看需要清除哪些内容。)

 else if (page.getUrl().regex(urlList).match()) {
//获取页面script并转码为中文
String origin = Unicode2utf8Utils.convert(page.getHtml().xpath("//head/script[7]").toString());
//从script中获取json
Matcher jsonMatcher = Pattern.compile("\\{.*\\}").matcher(origin);
//如果成功获取json数据
if (jsonMatcher.find()) {
//清理乱码
String jsonString = jsonMatcher.group().replaceAll("\"navEntries\".*?,", "")
.replaceAll(",\"p4pdata\".*?\\\"\\}\"", "").replaceAll("\"spuList\".*?,", "");
//选择auctions列表
List<String> auctions = new JsonPathSelector("mods.itemlist.data.auctions[*]").selectList(jsonString);

有关于JsonPathSelector的语法(JsonPath),可以参考这里JSONPath - XPath for JSON

使用方法即是新建一个JsonPathSelector并使用其select或selectList方法获取所求元素。

对于每条元素的处理,这里建议用阿里巴巴提供的fastjackson库去操作。

  //对于每一项商品
for (String auction : auctions) {
Map map = JSON.parseObject(auction);
//获取评论url
String commentUrl = (String) map.get("comment_url");
if (commentUrl == null) continue;
//获取商品id
Matcher itemIdMatcher = Pattern.compile("id=\\d+").matcher(commentUrl);
String itemIdString = null;
if (itemIdMatcher.find()) itemIdString = itemIdMatcher.group().replace("id=", "");
else continue;
//获取商店ip
String shopLink = new JsonPathSelector("shopLink").select(auction);
Matcher shopIdMatcher = Pattern.compile("user_number_id=\\d+").matcher(shopLink);
String shopIdString = null;
if (shopIdMatcher.find()) shopIdString = shopIdMatcher.group().replace("user_number_id=", "");
else continue;
//记录信息
map.put("itemId", itemIdString);
map.put("sellerId", shopIdString);
page.putField(itemIdString, map);

3. 爬取商品评论

至此我们的爬虫已经可以获取每个关键词的第一页商品信息,那么,如何获取其评论呢?注意到我在上段代码中获取了itemId和sellerId,利用这两个信息,我们可以获取评论。

打开一个评论页面,用浏览器的调试工具进行查看,会发现Network页面中的各种请求,我们可以挨个排查,查到,对于淘宝评论,将会发送如下请求:

https://rate.taobao.com/feedRateList.htm?auctionNumId=551058447857&userNumId=3167078258&currentPageNum=1&pageSize=20&rateType=&orderType=sort_weight&attribute=&sku=&hasSku=false&folded=0&ua=094%23UVQ6qM6U6l36u6ty666666BojjfaWoDLGsIU6Sf5RfSra4LjKWEeohUmxRUbiVNjH6Q6tusO%2Fbxm6M6QjLTM%2BR4t66W6nSkS1aQ6tHI6a486atAt6tlORWGWZHnBps8hHD80ee8tloiOPTTML6QtKBSv%2B6n0%2FxnKTeCbb1gD%2BlJiqwmPHGbgsSPNHG%2Fs%2FzmRR4pkHLd0%2BNJ9fpEJ%2FD76rYHg9yg9INGuG3hVxw01f9A2qP0vzP16Jjblbb%2FxxNnBuAmVHHEes9Jvkohr1G03Sv0yDg%2FAb9bnGzTspoo9%2B2raJp00HTElcncOzLSPIzvjT9n9zyoza5a2V7L%2BHpZYCWLYD7m%2F4est8Rws41d1V2R2D1jxDbS7Cn8Ez7C9w3FR3RoiAo0VcxtIsPgvI7SQVEjh9HS1bepYoRZep8Hws5zeVgc%2BApH6k0jKpgPs%2FzsBLuDczud0%2BNaAagcbpbHNvVTWALd414oEy3hV47Pv%2BU6Aa1ce1PZgkjc62Ty1ex77LRFAwLAk6M64jLTM%2B5PyzTXNAeTI09%2F0PkZvfRCGC15qjLTXi5Pyz9fNAehU09%2F0CRut66lLAeoM%2FDyr6M6ujLTWvMon0CR%3D&_ksTS=1498362441431_2073&callback=jsonp_tbcrate_reviews_list

对于天猫评论,我们可以发现如下请求:

https://rate.tmall.com/list_detail_rate.htm?itemId=549440936281&spuId=846223934&sellerId=1996270577&order=3&currentPage=1&append=0&content=1&tagId=&posi=&picture=&ua=096UW5TcyMNYQwiAiwQRHhBfEF8QXtHcklnMWc%3D%7CUm5Ockt%2FSnRPcEh0T3pCfCo%3D%7CU2xMHDJ7G2AHYg8hAS8XIw0tA18%2BWDRTLVd5L3k%3D%7CVGhXd1llXGhdY1hnX2NYbVVrXGFDf0tyT3FJdEF8RHBNcU1zSnRaDA%3D%3D%7CVWldfS0SMg02Dy8QMB4jHzFnMQ%3D%3D%7CVmhIGCUFOBgkGiIePgc6BzsbJxkiFzcDPwAgHCIZLAw5AzxqPA%3D%3D%7CV2xMHDJXLwEhHSIcPAEhHSMeJHIk%7CWGFBET8RMQo%2BBiYdKBAwCz4GPmg%2B%7CWWBAED4QMAgxCioWKREtDTcPMApcCg%3D%3D%7CWmNDEz0TMwoxCSkVKhQvDzUBNQBWAA%3D%3D%7CW2NDEz0TM2NaZVx8QH9Dfl5gWmBAfkN8XmJWblBsU2tLd0p%2FX2NbDS0QMB4wECUcIRxKHA%3D%3D%7CXGVYZUV4WGdHe0J%2BXmBYYkJ7W2VYeExsWXlDY19nMQ%3D%3D&isg=AoKCefv4bxJaWnPIzoTSiRgq04hIRrnyMb30i8ybA_WoHyOZtOJ-fD4dtS2Y&needFold=0&_ksTS=1498362526461_1756&callback=jsonp1757

分析这两个URL,我们可以发现,对于天猫连接,必要的属性为itemId,sellerId和currentPage,前两个可以从商品信息的comment_url和shopLink两个属性中通过正则匹配获取到,最后一个是页数。淘宝的连接也十分类似,只是属性名称有所变动。因此,我们可以针对这两种连接发送AJAX请求。自己重新用这些属性构造连接后,能够成功获得评论信息。

                    if (!map.get("comment_count").toString().isEmpty()) {
if(commentUrl.contains("taobao")){
for (int i = 1; i <= 5; ++i) {
String taoBaoUrl = "https://rate.taobao.com/feedRateList.htm?auctionNumId=" + itemIdString + "&userNumId=" + shopIdString + "&currentPageNum=" + i;
page.addTargetRequest(taoBaoUrl);
}
}else {
for (int i = 1; i <= 5; ++i) {
String tmallUrl = "https://rate.tmall.com/list_detail_rate.htm?itemId=" + itemIdString + "&sellerId=" + shopIdString + "&currentPage=" + i;
page.addTargetRequest(tmallUrl);
}
}
}

对于评论信息的获取如下:

 if (page.getUrl().regex(tmallComment).match()) {
String text = page.getRawText().replace("\"rateDetail\":", "");
//记录信息
Map map = JSON.parseObject(text);
if (map.get("rateList") == null) return;
Matcher itemIdMatcher = Pattern.compile("itemId=\\d+").matcher(page.getRequest().getUrl());
String itemIdString = null;
if (itemIdMatcher.find()) itemIdString = itemIdMatcher.group().replace("itemId=", "");
Matcher shopIdMatcher = Pattern.compile("sellerId=\\d+").matcher(page.getRequest().getUrl());
String shopIdString = null;
if (shopIdMatcher.find()) shopIdString = shopIdMatcher.group().replace("sellerId=", "");
Matcher currentPageMatcher = Pattern.compile("currentPage=\\d+").matcher(page.getRequest().getUrl());
String currentPageString = null;
if (currentPageMatcher.find()) currentPageString = currentPageMatcher.group().replace("currentPage=", "");
map.put("currentPage",currentPageString);
map.put("itemId", itemIdString);
map.put("sellerId", shopIdString);
map.put("url", page.getRequest().getUrl());
page.putField(itemIdString, map);
} else if (page.getUrl().regex(tbComment).match()) {
Matcher jsonMatcher = Pattern.compile("\\{.*\\}").matcher(page.getRawText());
if (jsonMatcher.find()) {
Map map = JSON.parseObject(jsonMatcher.group());
//如果触发反爬虫,报错
if (map.get("url") != null && map.get("url").toString().matches(urlSec)) {
System.out.println("Meet the anti-Spider!");
return;
}
if (map.get("comments") == null) return;
Matcher itemIdMatcher = Pattern.compile("auctionNumId=\\d+").matcher(page.getRequest().getUrl());
String itemIdString = null;
if (itemIdMatcher.find()) itemIdString = itemIdMatcher.group().replace("auctionNumId=", "");
Matcher shopIdMatcher = Pattern.compile("userNumId=\\d+").matcher(page.getRequest().getUrl());
String shopIdString = null;
if (shopIdMatcher.find()) shopIdString = shopIdMatcher.group().replace("userNumId=", "");
Matcher currentPageMatcher = Pattern.compile("currentPageNum=\\d+").matcher(page.getRequest().getUrl());
String currentPageString = null;
if (currentPageMatcher.find()) currentPageString = currentPageMatcher.group().replace("currentPageNum=", "");
map.put("currentPage",currentPageString);
map.put("itemId", itemIdString);
map.put("sellerId", shopIdString);
map.put("url", page.getRequest().getUrl());
page.putField(itemIdString, map);
}
}

这里可以看到,处理淘宝评论和天猫评论的过程是十分相似的,但是天猫评论没有反爬,而淘宝评论会有反爬手段,目前我的一些简单的规避反爬虫的方法都不奏效,也未能推测出反爬的方法,因此这个爬虫几乎只能获取一条商品一页的淘宝评论。想获取更多,还要想出躲避反爬的方法。因此我标题里仅说天猫评论。

4. 数据清洗

webmagic的Pipeline是可以自定义的,因而可以在其中进行数据清洗工作,以使数据在爬取后自动清洗。这里给出我自定义的Pipeline:

public class MyTBJsonPipeline extends FilePersistentBase implements Pipeline {
public MyTBJsonPipeline(String path) {
this.setPath(path);
} @Override
public void process(ResultItems resultItems, Task task) {
try {
Iterator iterator = resultItems.getAll().values().iterator();
while (iterator.hasNext()) {
Map map = (Map) iterator.next();
String name = map.get("itemId").toString();
if (map.get("raw_title") == null) {
if (map.get("rateList")!=null)
name += "_tmall_comment";
else name += "_taobao_comment";
name+="_"+map.get("currentPage");
}
PrintWriter printWriter = new PrintWriter(new FileWriter(this.getFile(path + name + ".json")));
printWriter.write(JSON.toJSONString(map));
printWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

我针对每一条商品都存储了单独的文件,并将评论单独存储(文件名有所关联),进而能够清晰的展示信息。

完整代码见个人github:https://github.com/CieloSun/FashionSpider

利用webmagic获取天猫评论的更多相关文章

  1. 第14.5节 利用浏览器获取的http信息构造Python网页访问的http请求头

    一. 引言 在<第14.3节 使用google浏览器获取网站访问的http信息>和<第14.4节 使用IE浏览器获取网站访问的http信息>中介绍了使用Google浏览器和IE ...

  2. (转)利用libcurl获取新浪股票接口, ubuntu和openwrt实验成功(三)

    1.  利用 CURLOPT_WRITEFUNCTION 设置回调函数, 利用 CURLOPT_WRITEDATA 获取数据指针 官网文档如下 CALLBACK OPTIONS CURLOPT_WRI ...

  3. Atitit利用反射获取子类 集合 以及继承树

    Atitit利用反射获取子类 集合 以及继承树 想从父类往下找子类的确是不可能的,要知道只要类不是final的话谁都有继承它的自由不需要事前通知父类. Eclipse实现不是重父类开始找而是重子类往回 ...

  4. 十九、利用OGNL获取ValueStack中:根栈和contextMap中的数据

    利用OGNL获取ValueStack中:根栈和contextMap中的数据 原则:OGNL表达式如果以#开头,访问的contextMap中的数据 如果不以#开头,是访问的根栈中的对象的属性(List集 ...

  5. c#反射机制学习和利用反射获取类型信息

    反射(Reflection)是.NET中的重要机制,通过放射,可以在运行时获得.NET中每一个类型(包括类.结构.委托.接口和枚举等)的成员,包括方法.属性.事件,以及构造函数等.还可以获得每个成员的 ...

  6. Cocos2d-x利用CCHttpRequest获取网络图片并显示

    利用CCHttpRequest获取网上http地址的图片并缓存到本地生成CCSprite用于显示 //图片结构class imgstruct : public CCObject { public: i ...

  7. 利用ParameterizedType获取泛型参数类型

    //利用ParameterizedType获取java泛型的参数类型 public class Demo {     public static void main(String[] args) { ...

  8. 利用GPS获取行车速度和距离

    这几天项目中需要GPS计算汽车的速度和行驶距离,这里简单记录一下使用过程 1 和平常使用地图一样,在Info.plist中添加位置请求 2 在viewdidLoad中初始化locationManage ...

  9. c#利用HttpWebRequest获取网页源代码

    c#利用HttpWebRequest获取网页源代码,搞了好几天终于解决了,直接获取网站编码进行数据读取,再也不用担心乱码了! 命名空间:Using System.Net private static ...

随机推荐

  1. Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送

    Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送, ...

  2. 【嵌入式开发】C语言 内存分配 地址 指针 数组 参数 实例解析

    . Android源码看的鸭梨大啊, 补一下C语言基础 ... . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/detai ...

  3. AngularJS进阶(二十六)实现分页操作

    JS实现分页操作 前言 项目开发过程中,进行查询操作时有可能会检索出大量的满足条件的查询结果.在一页中显示全部查询结果会降低用户的体验感,故需要实现分页显示效果.受前面"JS实现时间选择插件 ...

  4. SpriteBuilder中音频文件格式的需要注意的地方

    就像在SpriteBuilder项目子目录中的其他资源文件一样,音频文件夹需要确定完整的文件夹路径. 并且如果音频文件输出格式为MP4,则扩展为.m4a(audio-only MPEG4)而不是.mp ...

  5. Learning ROS forRobotics Programming Second Edition学习笔记(八)indigo rviz gazebo

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...

  6. dos2unix(windows脚本文件放到unix下运行要注意)

    在windows下编写的shell脚本文件,直接放到linux下运行,是不行的. infiniDB的倒库脚本文件load.sh,将tbl文件导入infiniDB,怎么运行不成功,不建job.运来,是w ...

  7. ARM linux常用汇编语法

    汇编语言每行的语法:     lable: instruction  ; comment 段操作: .section           格式: .section 段名 [标志]     [标志]可以 ...

  8. "《算法导论》之‘线性表’":基于数组实现的单链表

    对于单链表,我们大多时候会用指针来实现(可参考基于指针实现的单链表).现在我们就来看看怎么用数组来实现单链表. 1. 定义单链表中结点的数据结构 typedef int ElementType; cl ...

  9. Volley解析之表单提交篇

    要实现表单的提交,就要知道表单提交的数据格式是怎么样,这里我从某知名网站抓了一条数据,先来分析别人提交表单的数据格式.  数据包: Connection: keep-alive Content-Len ...

  10. ITU-T Technical Paper: NP, QoS 和 QoE的框架以及它们的区别

    本文翻译自ITU-T的Technical Paper:<How to increase QoS/QoE of IP-based platform(s) to regionally agreed ...