一、前言

自如房屋详情页的价格字段用图片显示,特此破解一下以丰富一下爬虫笔记系列博文集。

二、分析 & 实现

先打开一个房屋详情页观察一下;

网页的源代码中没有直接显示价格字段,价格的显示是使用一张背景图,图上是0-9十个数字,然后网页上显示的时候价格的每一个数字对应着一个元素,元素的背景图就设置为这张图片,然后使用偏移定位到自己对应的数字:

就拿上面这个例子来说,它对应的背景图是:

这张图宽30*10=300px,每个数字宽度是30px,网页上价格每个元素实际显示的数字在图片中数字的下标映射公式为:

  1. Math.abs(style_background-position_value) / 30

拿这个房屋价格代入:

  1. 第一个数字的background-position:-30px,带入得1,对应背景图中的第1个数字(下标从0开始),即为1
  2. 第二个数字的background-position:-60px,带入得2,对应背景图中的第2个数字,即为9
  3. 第三个数字的background-position:-90px,带入得3,对应背景图中的第3个数字,即为3
  4. 第四个数字的background-position:-240px,带入得8,对应背景图中的第8个数字,即为0

拼接起来得到最终价格:1930,与页面上显示的价格吻合。

其实并没有那么复杂,每一位对应图片中的数字的下标并不需要自己根据css计算,这个对应下标是在详情页的接口中返回的:

price是个数组,第一个元素是背景图的小图,第二个元素是背景图的大图,第三个元素是价格字段对应背景图中的第几个数字,有这几个信息足够识别出价格字段了,先从背景图中将价格对应的数字图片割出来,然后识别出来按顺序拼接起来再转为数字即可。

下面是识别价格字段的一个小Demo,依赖了我之前写的一个字符图片识别的小工具:commons-simple-character-ocr

源码:

  1. package cc11001100.crawler.ziroom;
  2.  
  3. import cc11001100.ocr.OcrUtil;
  4. import cc11001100.ocr.clean.SingleColorFilterClean;
  5. import cc11001100.ocr.split.ImageSplitImpl;
  6. import cc11001100.ocr.util.ImageUtil;
  7. import com.alibaba.fastjson.JSONArray;
  8. import com.alibaba.fastjson.JSONObject;
  9. import org.apache.logging.log4j.LogManager;
  10. import org.apache.logging.log4j.Logger;
  11. import org.jsoup.Jsoup;
  12.  
  13. import javax.imageio.ImageIO;
  14. import java.awt.image.BufferedImage;
  15. import java.io.ByteArrayInputStream;
  16. import java.io.IOException;
  17. import java.util.ArrayList;
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21.  
  22. import static com.alibaba.fastjson.JSON.parseObject;
  23. import static java.util.stream.Collectors.joining;
  24.  
  25. /**
  26. * 自如的房租价格用图片显示,这是一个从图片中解析出价格的例子
  27. *
  28. *
  29. * <a>http://www.ziroom.com/z/vr/250682.html</a>
  30. *
  31. * @author CC11001100
  32. */
  33. public class ZiRoomPriceGrab {
  34.  
  35. private static final Logger log = LogManager.getLogger(ZiRoomPriceGrab.class);
  36.  
  37. private static SingleColorFilterClean singleColorFilterClean = new SingleColorFilterClean(0XFFA000);
  38. private static ImageSplitImpl imageSplit = new ImageSplitImpl();
  39. private static Map<Integer, String> dictionaryMap = new HashMap<>();
  40.  
  41. static {
  42. dictionaryMap.put(-2132100338, "0");
  43. dictionaryMap.put(-458583857, "1");
  44. dictionaryMap.put(913575273, "2");
  45. dictionaryMap.put(803609598, "3");
  46. dictionaryMap.put(-1845065635, "4");
  47. dictionaryMap.put(1128997321, "5");
  48. dictionaryMap.put(-660564186, "6");
  49. dictionaryMap.put(-1173287820, "7");
  50. dictionaryMap.put(1872761224, "8");
  51. dictionaryMap.put(-1739426700, "9");
  52. }
  53.  
  54. public static JSONObject getHouseInfo(String id, String houseId) {
  55. String url = "http://www.ziroom.com/detail/info?id=" + id + "&house_id=" + houseId;
  56. String respJson = downloadText(url);
  57. if (respJson == null) {
  58. throw new RuntimeException("response null, id=" + id + ", houseId=" + houseId);
  59. }
  60. return parseObject(respJson);
  61. }
  62.  
  63. private static int extractPrice(JSONObject houseInfo) throws IOException {
  64. JSONArray priceInfo = houseInfo.getJSONObject("data").getJSONArray("price");
  65. String priceRawImgUrl = "http:" + priceInfo.getString(0);
  66. System.out.println("priceRawImgUrl: " + priceRawImgUrl);
  67. JSONArray priceImgCharIndexArray = priceInfo.getJSONArray(2);
  68. System.out.println("priceImgCharIndexArray: " + priceImgCharIndexArray);
  69. BufferedImage img = downloadImg(priceRawImgUrl);
  70. if (img == null) {
  71. throw new RuntimeException("img download failed, url=" + priceRawImgUrl);
  72. }
  73. List<BufferedImage> priceCharImgList = extractNeedCharImg(img, priceImgCharIndexArray);
  74. String priceStr = priceCharImgList.stream().map(charImg -> {
  75. int charImgHashCode = ImageUtil.imageHashCode(charImg);
  76. return dictionaryMap.get(charImgHashCode);
  77. }).collect(joining());
  78. return Integer.parseInt(priceStr);
  79. }
  80.  
  81. // 因为价格通常是4位数,而返回的图片有10位数(0-9),所以第一步就是将价格字符抠出来
  82. // (或者也可以先全部识别为字符串然后从字符串中按下标选取)
  83. private static List<BufferedImage> extractNeedCharImg(BufferedImage img, JSONArray charImgIndexArray) {
  84. List<BufferedImage> allCharImgList = imageSplit.split(singleColorFilterClean.clean(img));
  85. List<BufferedImage> needCharImg = new ArrayList<>();
  86. for (int i = 0; i < charImgIndexArray.size(); i++) {
  87. int index = charImgIndexArray.getInteger(i);
  88. needCharImg.add(allCharImgList.get(index));
  89. }
  90. return needCharImg;
  91. }
  92.  
  93. private static byte[] downloadBytes(String url) {
  94. for (int i = 0; i < 3; i++) {
  95. long start = System.currentTimeMillis();
  96. try {
  97. byte[] responseBody = Jsoup.connect(url)
  98. .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36")
  99. .ignoreContentType(true)
  100. .execute()
  101. .bodyAsBytes();
  102. long cost = System.currentTimeMillis() - start;
  103. log.info("request ok, tryTimes={}, url={}, cost={}", i, url, cost);
  104. return responseBody;
  105. } catch (Exception e) {
  106. long cost = System.currentTimeMillis() - start;
  107. log.info("request failed, tryTimes={}, url={}, cost={}, cause={}", i, url, cost, e.getMessage());
  108. }
  109. }
  110. return null;
  111. }
  112.  
  113. private static String downloadText(String url) {
  114. byte[] respBytes = downloadBytes(url);
  115. if (respBytes == null) {
  116. return null;
  117. } else {
  118. return new String(respBytes);
  119. }
  120. }
  121.  
  122. private static BufferedImage downloadImg(String url) throws IOException {
  123. byte[] imgBytes = downloadBytes(url);
  124. if (imgBytes == null) {
  125. return null;
  126. }
  127. return ImageIO.read(new ByteArrayInputStream(imgBytes));
  128. }
  129.  
  130. private static void init() {
  131. // OcrUtil ocrUtil = new OcrUtil().setImageClean(new SingleColorFilterClean(0XFFA000));
  132. // ocrUtil.init("H:/test/crawler/ziroom/raw/", "H:/test/crawler/ziroom/char/");
  133. OcrUtil.genAndPrintDictionaryMap("H:/test/crawler/ziroom/char/", "dictionaryMap", filename -> filename.substring(0, 1));
  134. }
  135.  
  136. public static void main(String[] args) throws IOException {
  137. // init();
  138.  
  139. JSONObject o = getHouseInfo("61718150", "60273500");
  140. int price = extractPrice(o);
  141. System.out.println("price: " + price); // 1930
  142.  
  143. // output:
  144. // 2018-12-15 20:24:59.206 INFO cc11001100.crawler.ziroom.ZiRoomPriceGrab 103 downloadBytes - request ok, tryTimes=0, url=http://www.ziroom.com/detail/info?id=61718150&house_id=60273500, cost=559
  145. // priceRawImgUrl: http://static8.ziroom.com/phoenix/pc/images/price/ba99db25b3be2abed93c50c7f55c332cs.png
  146. // priceImgCharIndexArray: [6,3,8,1]
  147. // 2018-12-15 20:24:59.538 INFO cc11001100.crawler.ziroom.ZiRoomPriceGrab 103 downloadBytes - request ok, tryTimes=0, url=http://static8.ziroom.com/phoenix/pc/images/price/ba99db25b3be2abed93c50c7f55c332cs.png, cost=146
  148. // price: 1930
  149.  
  150. }
  151.  
  152. }

三、总结

自如的房屋价格图片显示类似于新蛋的商品价格图片显示,此类反爬措施破解难度较低,比较致命的是破解方案具有通用性,这意味着随便找个图片识别的库怼上就行,所以还不如自研个比较复杂的js加密来反爬呢,你要想高效的爬取就得来分析js折腾半天,反爬机制对应的破解方案应该不具有通用性并且成本比较高这个反爬做得才有意义,否则爬虫方面投入很小的成本(时间 & 经济上的投入)就破解了那这反爬相当于白做哇。

相关资料:

1. 电商站新蛋价格字段爬取(价格字段图片显示)

2. commons-simple-character-ocr

.

爬虫笔记之自如房屋价格图片识别(价格字段css背景图片偏移显示)的更多相关文章

  1. div css背景图片不显示

    我们在写页面时,为了便于维护,css样式通常都是通过link外部导入html的,有时在css中写入背景图片时,此时背景图片的路径应该是相对css文件的.比如,此时的文件有index.html,css. ...

  2. css 3 背景图片为渐变色(渐变色背景图片) 学习笔记

    6年不研究CSS发现很多现功能都没有用过,例如渐变色,弹性盒子等,年前做过一个简单的管理系统,由于本人美工不好,设计不出好看的背景图片,偶然百度到背景图片可以使用渐变色(感觉发现了新大陆).以后的项目 ...

  3. Bootstrap css背景图片的设置

    一. 网页中添加图片的方式有两种 一种是:通过<img>标签直接插入到html中 另一种是:通过css背景属性添加 居中方法:水平居中的text-align:center 和 margin ...

  4. css背景图片拉伸 以及100% 满屏显示

    如何用css背景图片拉伸 以及100% 满屏显示呢?这个问题听起来似乎很简单.但是很遗憾的告诉大家.不是我们想的那么简单. 比如一个容器(body,div,span)中设定一个背景.这个背景的长宽值在 ...

  5. CSS背景图片定位

    原文:CSS背景图片定位 在网页开发中我们经常需要对图片进行分割(如下图)来使用,而不是分别提供单独的图片来调用,常见的如页面背景,按钮图标等,这样做的好处就是减少请求次数,节省时间和带宽. 对背景图 ...

  6. 【IE6的疯狂之八】链接伪类(:hover)CSS背景图片有闪动BUG

    IE6下链接伪类(:hover)CSS背景图片有闪动BUG,主要原因ie会再一次请求这张图片,或者说图片没被缓存. 例如: CSS代码 a:hover{background:url(imagepath ...

  7. 【转】链接伪类(:hover)CSS背景图片有闪动BUG

    来源:http://www.css88.com/archives/744 --------------------------------------------------------------- ...

  8. 兼容各浏览器的css背景图片拉伸代码

    需要用到背景图拉伸,找到了下面这段css代码: filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='***.jpg' , s ...

  9. css背景图片拉伸

    css背景图片拉伸 background-image:url(bg.png); -moz-background-size: 100% 100%; -o-background-size: 100% 10 ...

随机推荐

  1. Fragment 使用总结

    1. 要深刻理解Fragment 的生命周期 2. Fragment.getActivity()并不能保证非空. 3.如果在Fragment中有异步的回调, 特别要注意此时Fragment 是否还at ...

  2. Linux内核分析第三周总结

    构造一个简单的Linux系统MenuOS 操作系统的"两把宝剑":中断上下文的切换(保存现场和恢复现场).进程上下文的切换 Linux内核源代码简介 --------------- ...

  3. 冲刺Two之站立会议7

    今天我们把软件的基本功能完成之后,又对所有的界面进行了统一规范化并进行了相应的优化.

  4. javascript 数组对象及其方法

    数组声明:通过let arr = new Array(); 或者 let arr = []; 数组对象可调用的方法: 1)find方法,使用情况是对数组进行筛选遍历,find方法要求某个函数(A)作为 ...

  5. 构建之法——Team & Scrum & MSF

        第五章(团队和流程)83-99  这一章主要介绍的是团队精神 那是不是说只要能组合在一起的就是组成了一个团队了?其实不然,软件团队有各种形式,适用于不同的人员和需求.适合自己的团队才能共赢! ...

  6. 第二个Sprint冲刺第三天(燃尽图)

  7. 项目复审——Beta阶段

    排名原则还是基于这个组到底自己做了多少东西,又借鉴了多少东西,不过其他组的具体情况我也不一定说的清楚,所以只是通过大家的码云和一些了解来评判的.当然,是否发布也是一个重要指标.顺便感叹一句,现在的云平 ...

  8. 组件 -- Badge

    .badge :长方形的徽章 badge的颜色: .badge-primary .badge-secondary .badge-success .badge-warning ... ... .badg ...

  9. Spring之AOP操作术语说明

  10. 用jq获取元素内文本,但不包括其子元素内的文本值的方法

    <li id="listItem"> This is some text <span id="firstSpan">First span ...