爬虫之邮箱混淆
一、為什么需要郵箱混淆
先來解釋一下什么是郵箱混淆,郵箱混淆就是對頁面上的郵箱進行處理,使用JS加密、HTML隱藏元素干擾、圖片顯示等方式增加爬蟲獲取的難度。很多人都有過這種體驗,當在網絡上留下自己的郵箱之后,過不了多久這個郵箱就會收到一堆亂七八糟的垃圾郵件,都是一些廣告、詐騙信息等。這是因為每時每刻都有爬蟲在掃描互聯網上的郵箱對其推送垃圾信息,應對這種情況,一般會有兩種應對策略,一種就是盡量不在網絡上留下自己的郵箱,這種辦法屬于鴕鳥政策,因為害怕所以就逃避不去面對這個問題,這個解決方案不大好。另一種辦法就是雖然留下郵箱,但是刻意去增加爬蟲獲取信息的難度同時盡量不去增加人獲取信息的難度,即盡量讓人閱讀友好但程序解析困難。本篇文章主要圍繞第二種方式闡述增加爬蟲獲取郵箱難度的幾種方法。
雖然本文是在講郵箱混淆,但這個問題其實可以抽象為短文本如何反爬,即如何保護用戶的重要信息字段不被爬蟲獲取,這些字段也可以是手機號、QQ號、居住地等等。
二、如何增加爬蟲獲取信息難度
2.1 留郵箱時破壞格式
對于增加爬蟲獲取郵箱的難度,大致可分為兩類,一種是留下郵箱的人在留的過程中對其格式破壞增加爬蟲識別難度,比如我的郵箱是foo@bar.com,那么我留下郵箱的時候就會留下比如foo#bar.com,這種是比較流行的格式,人一看就知道是個郵箱地址,但是就是太過于簡單人一看就知道是郵箱地址,爬蟲也能,對于這種格式的,爬蟲只需要多加一個模式匹配即可兼容。那好吧,現在為了增加爬蟲識別的難度,我寫的變態一點,寫成這種方式:
foo 艾特 bar 點 com
這種方式文本噪音沒有固定模式,是增加了爬蟲的解析難度,但是也增加人獲取信息的難度。不過此類方式或許還是有用武之地,因為看到一些發廣告信息的人留聯系郵箱時為了逃避垃圾郵箱過濾,會使用這種方式混淆讓過濾系統認為自己不是郵箱。
好吧,看來用留文本的方式無論如何是不能完美的搞定爬蟲了,那么我留一個圖片好了,我的郵箱還是foo@bar.com,這次我留下郵箱不放文本了,我把它從記事本打出來然后截個圖我放圖片:
image
嗯,上面的字體整整齊齊,人識別的難度很低,同樣機器識別的難度也很低,如果爬蟲刻意針對此類圖片掃描一下內容的話是可以完美識別的,這樣做也沒多大用,如果圖片不加混淆的話基本等同于使用文本的方式對抗爬蟲,可如果增加噪點、干擾線、扭曲字體等進行混淆的話,同樣增加爬蟲識別難度的同時也增加了人獲取信息的難度,因為圖片不能復制,如果再加了混淆,一不小心就可能打錯一位,這種方式算是很不友好了。
2.2 平臺負責守衛郵箱安全
郵箱總是要顯示在某個平臺上的,比如我留在百度貼吧的郵箱,百度貼吧就要負責對其進行保護,不讓爬蟲等進行識別到。亦或者某個網站用戶注冊時留的聯系郵箱,在查看個人信息時能夠看到,那么這個平臺就必須要負責此郵箱的混淆,不然如果某個人遍歷所有用戶個人信息得到郵箱挨個推送垃圾信息用戶投訴平臺帶來負面影響怎么辦,所以在設計產品的時候也要考慮到這些因素,不過現在的產品都比較注重個人隱私,基本都不把郵箱信息公開顯示了,比如有些平臺會做成好友可見之類的,然而沒卵用,只要能夠讓除了自己以外的人看到,都會有辦法獲取到,只不過手段可能有點無恥,有點突破底線。就拿信息好友可見舉個例子,如果有個大胸美女頭像的人加你好友,你點進去一看她的歷史記錄是個文藝女青年,正是你喜歡的菜,你會不會拒絕她的好友請求呢?同理只需要多搞一些馬甲,馬甲多樣化,覆蓋大多數人的愛好就可以了,此種方式還是能夠獲取到一部分郵箱的,當然要平臺足夠大才值得搞,用戶都沒幾個的還是算了。呃,好像跑題了,下一部分會具體的講平臺保護用戶郵箱的幾種方式。
三、平臺郵箱混淆的方式
下文會介紹一些郵箱混淆的方法,同時針對每種方法做一些簡單的實現,出于簡便考慮實驗使用的WEB環境為Spark Web Framework。
3.1 使用HTML+CSS混淆
3.1.1 添加不可見元素
在郵箱的各個字符之間穿插不可見元素,瀏覽器渲染出來的只有可見字符,而爬蟲不會去渲染樣式,很有可能就連隱藏元素的內容也一起算作郵箱地址的一部分而解析到錯誤的郵箱地址。
下面是一個小例子,比如在服務器返回數據的時候對郵箱處理在字符之間隨機插入一些隱藏的HTML元素:
package cc11001100.crawler;
import spark.Spark;
import java.util.Random;
/**
-
@author CC11001100
*/
public class EmailProtectionHideElementDemo {private static String emailProtection(String email) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < email.length(); i++) {
result.append(email.charAt(i));
if (Math.random() < 0.5) {
result.append(genHideHtmlElement(email));
}
}
return result.toString();
}private static String genHideHtmlElement(String rawEmail) {
StringBuilder mixContent = new StringBuilder();
Random random = new Random();
for (int i = 0, end = Math.min(rawEmail.length(), 3); i < end; i++) {
mixContent.append(rawEmail.charAt(random.nextInt(rawEmail.length())));
}
return “” + mixContent.toString() + “”;
}public static void main(String[] args) {
Spark.get("/show_email", (req, resp) -> “” + emailProtection(“foo@bar.com”) + “
”);
}
}
前端頁面的顯示效果,用戶在瀏覽器中看到的是正常的,但是爬蟲解析的話很有可能就解析到錯誤的:
image
順帶討論下對于使用HTML混淆網上比較流行的說法使用HTML注釋進行混淆,將上面的例子修改一下,由隱藏元素改為使用注釋進行混淆:
package cc11001100.crawler;
import spark.Spark;
import java.util.Random;
/**
-
@author CC11001100
*/
public class EmailProtectionCommentDemo {private static String emailProtection(String email) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < email.length(); i++) {
result.append(email.charAt(i));
if (Math.random() < 0.5) {
result.append(genHideHtmlElement(email));
}
}
return result.toString();
}private static String genHideHtmlElement(String rawEmail) {
StringBuilder mixContent = new StringBuilder();
Random random = new Random();
for (int i = 0, end = Math.min(rawEmail.length(), 3); i < end; i++) {
mixContent.append(rawEmail.charAt(random.nextInt(rawEmail.length())));
}
// 混淆元素使用注釋
return “ ”;
}public static void main(String[] args) {
Spark.get("/show_email", (req, resp) -> “” + emailProtection(“foo@bar.com”) + “
”);
}
}
然后啟動WEB應用,編寫代碼使用Jsoup爬取一下:
package cc11001100.crawler;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
-
@author CC11001100
*/
public class EmailCrawler {private static List extract(String url) {
try {
return Jsoup.parse(new URL(url), 1000).select(“p”)
.stream()
.flatMap(element -> EmailCrawler.extractEmail(element).stream())
.collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
return Collections.emptyList();
}private static List extractEmail(Element contentElt) {
Pattern pattern = Pattern.compile("[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+");
Matcher matcher = pattern.matcher(contentElt.text());
List result = new ArrayList<>();
while (matcher.find()) {
result.add(matcher.group());
}
return result;
}public static void main(String[] args) {
String url = “http://localhost:4567/show_email”;
extract(url).forEach(System.out::println);
}
}
發現沒有進行任何額外處理就取出了正確的郵箱地址,JSoup解析DOM的框架取元素的text時會忽略HTML注釋中的內容,下面是JSoup中text()的實現:
/**
-
Gets the combined text of this element and all its children.
-
-
For example, given HTML {@code
Hello there now!
}, {@code p.text()} returns {@code “Hello there now!”} -
@return unencoded text, or empty string if none.
-
@see #ownText()
-
@see #textNodes()
public void tail(Node node, int depth) {}
*/
public String text() {
final StringBuilder accum = new StringBuilder();
new NodeTraversor(new NodeVisitor() {
public void head(Node node, int depth) {
// 對于節點類型只取文本節點
if (node instanceof TextNode) {
TextNode textNode = (TextNode) node;
appendNormalisedText(accum, textNode);
} else if (node instanceof Element) {
// 普通標簽都是作為Element解析的,但是注釋類型有自己單獨的Comment類表示,Comment和Element都繼承Node,所以text()時注釋類型Comment會被忽略
Element element = (Element) node;
if (accum.length() > 0 &&
(element.isBlock() || element.tag.getName().equals(“br”)) &&
!TextNode.lastCharIsWhitespace(accum))
accum.append(" ");
}
}}).traverse(this);
return accum.toString().trim();
}
不過對于掃描郵箱的爬蟲一般都是不會去解析DOM的,這太浪費資源了,大部分都是對頁面內容進行簡單的正則匹配,對于正則掃描,使用HTML注釋混淆的方法還是有效的。
3.1.2 使用CSS改變文本方向
后臺返回郵箱的時候進行字符串反轉,前端顯示的時候對郵箱使用CSS樣式再反轉回來,用戶看到的就是正確的郵箱地址,而爬蟲一般是不會渲染CSS的,所以爬蟲爬取到的就是被反轉之后的錯誤郵箱地址。
但是這種方式有個致命的缺點就是雖然用戶看到的是正序的,但是如果選中復制的話復制到的內容仍然是逆序的,這也是CSS類反爬的典型缺點,所以并不是太推薦此種方式。
下面是一個小例子:
package cc11001100.crawler;
import spark.Spark;
/**
-
@author CC11001100
*/
public class EmailProtectionCssReverseDemo {private static String emailProtection(String email) {
return “” + reverse(email) + “”;
}private static String reverse(String s) {
StringBuilder result = new StringBuilder(s.length());
for (int i = s.length() - 1; i >= 0; i–) {
result.append(s.charAt(i));
}
return result.toString();
}public static void main(String[] args) {
Spark.get("/show_email", (req, resp) -> “” + emailProtection(“foo@bar.com”) + “
”);
}
}
顯示效果:
image
3.2 使用JS加密
JS混淆就是在服務器端對數據進行加密,然后在客戶端瀏覽器渲染的時候使用JS進行解密。使用JS進行混淆的方法就有很多了,可以自定義一套加密解密規則,不過這里需要遵守的原則就是盡量讓加密解密規則不具有通用性并且難于理解(解密JS混淆是必須的),不具有通用性針對一個站點要重新編寫代碼,考慮到成本爬蟲方面很可能會放棄,不放棄的話混淆代碼也很難懂,爬蟲方面看不懂也很有可能會放棄,這也只是理想情況下,JS解密畢竟將解密邏輯放在了客戶端,鐵了心要破解的話死磕硬剛總是能夠破解的。
對于JS加密因為方式太多了不再自己寫例子而是看一個現成的例子。
JS郵箱混淆例子: CDN開啟郵箱保護
百度CDN有一個功能叫做郵箱混淆, 當開啟了此功能之后頁面中的郵箱就會被替換為這種形式:
[email?protected]
這種如果直接解析的話只會得到一個[email protected],解析不到郵箱地址,郵箱地址是在頁面加載完成后通過JS渲染出來的。開啟了百度CDN的網站都會有一個路徑:
/cdn-cgi/scripts/f2bf09f8/cloudflare-static/email-decode.min.js
這個JS負責將鏈接解析為可讀的形式顯示在頁面上,看下email-decode.min.js的內容:
!function () {
“use strict”;
}();
下面是本人對email-decode.min.js的一個閱讀分析,修改了部分代碼以提高可讀性:
!function () {
“use strict”;
}();
針對上面的解密邏輯,可寫出破解代碼:
package cc11001100.crawler;
/**
-
@author CC11001100
*/
public class BaiDuCDNEmailProtectionCracker {private static String decodeEmail(String encodeEmailString) {
StringBuilder result = new StringBuilder(encodeEmailString.length() / 2 - 1);
int key = charToInt(encodeEmailString, 0);
for (int i = 2; i < encodeEmailString.length(); i += 2) {
char c = (char) (charToInt(encodeEmailString, i) ^ key);
result.append?;
}
return result.toString();
}private static int charToInt(String s, int from) {
return Integer.parseInt(s.substring(from, from + 2), 16);
}public static void main(String[] args) {
System.out.println(decodeEmail(“e38d8991819b9b999ba3908a8d82cd808c8e”));
}
}
另外值得一提的是百度和CloudFlare有合作關系,看這個鏈接
/cdn-cgi/scripts/f2bf09f8/cloudflare-static/email-decode.min.js
很有可能是CloudFlare將技術共享給了百度CDN。
3.3 使用圖片顯示郵箱
使用圖片顯示郵箱這種方式其實相當于把問題轉化為了字符型驗證碼識別問題。對于字符型驗證碼如果不加干擾線、扭曲之類的很容易就能識別出來,如果加了的話人識別的難度又會被增加,而且這種方式最致命的就是沒辦法復制,所以這種方式不推薦,這里也不再進行詳細闡述。
四、 總結
啰里啰嗦了這么多,下面總結一下。
image
相關資料:
Nine ways to obfuscate e-mail addresses compared
發布郵件地址時用「#」「at」等替代「@」有助于反垃圾郵件嗎?
.
總結
- 上一篇: 基 于 主 成 分分 析 和 灰 色 聚
- 下一篇: 数据结构-----迷宫问题(C语言)