前言:最近博主買了台Kindle,感覺亞馬遜上的圖書資源質量挺好,還時不時地會有價格低但質量高的書出售,但限於亞馬遜並沒有很好的優惠提醒功能,自己天天盯着又很累。於是,我自己寫了一個基於Java的亞馬遜圖書監控的簡單爬蟲,只要出現特別優惠的書便會自動給指定的郵箱發郵件。
實現思路
簡單地說一下實現的思路,本文只說明思路,需要完整項目的童鞋請移步文末
- 簡單封裝JavaMail,使發送郵件更加方便
- 讀取配置文件,用於配置郵件發送及監控設置
- 利用
URL類
返回的URLConnection對象
對網站進行訪問,抓取數據。(這里有個小技巧,在訪問亞馬遜的時候如果沒有在請求頭上加入Accept-Encoding:gzip, deflate, br
這個參數,則不出幾次便會被拒絕訪問(返回503),加上之后返回的數據是經GZIP壓縮過的,此時需要用GZIPInputStream
這個流去讀取,否則讀到的是亂碼) - 利用正則表達式分析獲取到的數據,提取有用信息。
- 發送通知郵件。
基本功能
- 監控銷量排行榜前50名的圖書價格,若出現價格低於設定值的圖書,則發送郵件通知。
- 帶有數據緩存功能,比如持續幾天的優惠只會在一開始時通知一次,並不會在每次抓取時都通知。
- 可以通過配置文件(setting.conf)動態更改郵件和監控的設置。
- 報錯信息會保存在ErrLog.txt中,一般的日志會保存在Log.txt中。
核心代碼
因為只截取了部分代碼,內容有所缺失,思路能看明白即可
抓取亞馬遜信息
this.url = new URL("https://www.amazon.cn/gp/bestsellers/digital-text");
//打開一個連接
URLConnection connection = this.url.openConnection();
//設置請求頭,防止被503
connection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
connection.setRequestProperty("Accept-Encoding", "gzip, deflate, br");
connection.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9");
connection.setRequestProperty("Host", "www.amazon.cn");
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36");
//發起連接
connection.connect();
//獲取數據,因為服務器發過來的數據經過GZIP壓縮,要用對應的流進行讀取
BufferedInputStream bis = new BufferedInputStream(new GZIPInputStream(connection.getInputStream()));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//讀數據
while ((len = bis.read(tmp)) != -1) {
baos.write(tmp, 0, len);
}
this.rawData = new String(baos.toByteArray(), "utf8");
bis.close();
用正則分析數據
//先用正則表達式去取單個li標簽
Pattern p1 = Pattern.compile("<li class=\"zg-item-immersion\"[\\s\\S]+?</li>");
Matcher m1 = p1.matcher(this.rawData == null ? "" : this.rawData);
while (m1.find()) {
//取出單個li標簽的名字和價格
Pattern p2 = Pattern.compile("alt=\"([\\u4E00-\\u9FA5:—,0-9a-zA-Z]+)[\\s\\S]+?¥(\\d{1,2}\\.\\d{2})");
Matcher m2 = p2.matcher(m1.group());
while (m2.find()) {
//先取出名字
String name = m2.group(1);
//再取出價格
double price = Double.parseDouble(m2.group(2));
//若有相同名字的書籍只記錄價格低的
if (this.destData.containsKey(name)) {
double oldPrice = this.destData.get(name).getPrice();
price = oldPrice > price ? price : oldPrice;
}
//將數據放入Map中
this.destData.put(name, new Price(price, new Date()));
}
}
完整項目
我把完整的項目放在了我的Github上,更多詳細情況(怎么配置、怎么用),有興趣的童鞋可以去捧個場!
倉庫地址:https://github.com/horvey/Amazon-BookMonitor