title: 爬蟲的故事 tags:
- WebMagic
- JFinal
- Java
- 爬蟲
- spider categories: 爬蟲 date: 2017-07-25 18:18:53
1、爬蟲佔總PV比例較高,這樣浪費錢(尤其是三月份爬蟲)。
三月份爬蟲是個什麼概念呢?每年的三月份我們會迎接一次爬蟲高峰期。
最初我們百思不得其解。直到有一次,四月份的時候,我們刪除了一個url,然後有個爬蟲不斷的爬取url,導致大量報錯,測試開始找我們麻煩。我們只好特意為這個爬蟲釋出了一次站點,把刪除的url又恢復回去了。
但是當時我們的一個組員表示很不服,說,我們不能幹掉爬蟲,也就罷了,還要專門為它釋出,這實在是太沒面子了。於是出了個主意,說:url可以上,但是,絕對不給真實資料。
於是我們就把一個靜態檔案釋出上去了。報錯停止了,爬蟲沒有停止,也就是說對方並不知道東西都是假的。這個事情給了我們一個很大的啟示,也直接成了我們反爬蟲技術的核心:變更。
後來有個學生來申請實習。我們看了簡歷發現她爬過攜程。後來面試的時候確認了下,果然她就是四月份害我們釋出的那個傢伙。不過因為是個妹子,技術也不錯,後來就被我們招安了。現在已經快正式入職了。
後來我們一起討論的時候,她提到了,有大量的碩士在寫論文的時候會選擇爬取OTA資料,並進行輿情分析。因為五月份交論文,所以嘛,大家都是讀過書的,你們懂的,前期各種DotA,LOL,到了三月份了,來不及了,趕緊抓資料,四月份分析一下,五月份交論文。
就是這麼個節奏。
2、公司可免費查詢的資源被批量抓走,喪失競爭力,這樣少賺錢。
O他的價格可以在非登入狀態下直接被查詢,這個是底線。如果強制登陸,那麼可以通過封殺賬號的方式讓對方付出代價,這也是很多網站的做法。但是我們不能強制對方登入。那麼如果沒有反爬蟲,對方就可以批量複製我們的資訊,我們的競爭力就會大大減少。
競爭對手可以抓到我們的價格,時間長了使用者就會知道,只需要去競爭對手那裡就可以了,沒必要來攜程。這對我們是不利的。
3、爬蟲是否涉嫌違法? 如果是的話,是否可以起訴要求賠償?這樣可以賺錢。
這個問題我特意諮詢了法務,最後發現這在國內還是個擦邊球,就是有可能可以起訴成功,也可能完全無效。所以還是需要用技術手段來做最後的保障。
-----不願透露姓名的攜程酒店研發團隊崔廣宇
現在都大資料時代了,不會點爬蟲怎麼裝13,沒爬過github,知乎,豆瓣,twitter,fb怎麼做資料探勘 ,使用者畫像
-----某海龜網際網路精英
目前爬蟲和反爬蟲國內環境大家還是不願宣之於口的,網上一搜大部分都是一段python程式碼,然後大家懂得。
比較常見的幾個領域:電商(jd,tb),招聘(51job),新聞,部落格,社群(douban),旅遊(tuniu,攜程),服務類(58),房地產(鏈家)等
目前我們團隊主要使用Java語言,給大家介紹一個比較簡便的爬蟲框架 webmagic 趁著週末玩一把(請勿用於非法行為)
幾個注意事項
- 注意設定UA
- 注意使用代理(更換ip)
- 注意處理請求頻率
- 注意處理失敗請求
- 使用排程機制,方便掛掉後不需要重新開始
- 隨時關閉
- 監控爬蟲狀態
- 分散式爬取
簡單的對 XXX 畫面進行了爬取
目前使用了XX-NET做代理伺服器工程師必備技能之科學上網
通過Jfinal來完成db的持久化
public class GitHubSpider implements PageProcessor {
private Site site = Site.me().setRetryTimes(3).setSleepTime(1000).setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12A365 MicroMessenger/5.4.1 NetType/WIFI");
private static final String brandUrl = "http://XXX/level/brand/getAllJsons";
private static final String manuUrl = "http://XXX/level/manufacturers/getManufacturers?brandId=#BRANDID";
private static final String modelUrl = "http://XXX/level/model/getModels?brandId=#BRANDID&manufacturersId=#MANUID";
private static final String saleUrl = "http://XXX/level/salesName/getSalesNames?brandId=#BRANDID&manufacturersId=#MANUID&modelId=#MODELID";
private static final String yearUrl = "http://XXX/level/year/getYears?brandId=#BRANDID&manufacturersId=#MANUID&modelId=#MODELID&salesName=#SALEID";
private static final String dataUrl = "http://XXX/level/fuchs/getData?compressId=#COMPRESSID";
private static final String dataDetailAddressUrl = "http://XXX/level/fuchs/gotoDetailPage?productName=#PRODUECTNAME";
private static final String dataDetailUrl = "http://XXX/#PRODUCTADDRESS";
private static final JsonPathSelector brandIdSelector = new JsonPathSelector("$.value[*].brandId");
private static final JsonPathSelector manuIdSelector = new JsonPathSelector("$.value[*].manufacturersId");
private static final JsonPathSelector modelSelector = new JsonPathSelector("$.value[*].modelsId");
private static final JsonPathSelector saleIdSelector = new JsonPathSelector("$.value[*].salesName");
private static final JsonPathSelector yearIdSelector = new JsonPathSelector("$.value[*].compressId");
@Override
public void process(Page page) {
String url = page.getUrl().get();
if (url.contains("getAllJsons")) {
page.addTargetRequests(Lists.transform(brandIdSelector.selectList(page.getRawText()), new Function<String, String>() {
@Override
public String apply(String brandId) {
return manuUrl.replace("#BRANDID", encodeParam(brandId));
}
}));
page.setSkip(true);
} else if (url.contains("getManufacturers")) {
final String brandId = page.getUrl().regex(".*brandId=(.*)", 1).get();
page.addTargetRequests(Lists.transform(manuIdSelector.selectList(page.getRawText()), new Function<String, String>() {
@Override
public String apply(String manuId) {
return modelUrl.replace("#MANUID", encodeParam(manuId)).replace("#BRANDID", brandId);
}
}));
page.setSkip(true);
} else if (url.contains("getModels")) {
final String brandId = page.getUrl().regex(".*brandId=(.*)&manufacturersId.*", 1).get();
final String manuId = page.getUrl().regex(".*manufacturersId=(.*)", 1).get();
page.addTargetRequests(Lists.transform(modelSelector.selectList(page.getRawText()), new Function<String, String>() {
@Override
public String apply(String modelId) {
return saleUrl.replace("#MODELID", encodeParam(modelId)).replace("#MANUID", manuId).replace("#BRANDID", brandId);
}
}));
page.setSkip(true);
} else if (url.contains("getSalesNames")) {
final String brandId = page.getUrl().regex(".*brandId=(.*)&manufacturersId.*", 1).get();
final String manuId = page.getUrl().regex(".*manufacturersId=(.*)&modelId.*", 1).get();
final String modelId = page.getUrl().regex(".*modelId=(.*)", 1).get();
page.addTargetRequests(Lists.transform(saleIdSelector.selectList(page.getRawText()), new Function<String, String>() {
@Override
public String apply(String saleName) {
return yearUrl.replace("#SALEID", encodeParam(saleName)).replace("#MODELID", modelId).replace("#MANUID", manuId).replace("#BRANDID", brandId);
}
}));
page.setSkip(true);
} else if (url.contains("getYears")) {
page.addTargetRequests(Lists.transform(yearIdSelector.selectList(page.getRawText()), new Function<String, String>() {
@Override
public String apply(String compressId) {
return dataUrl.replace("#COMPRESSID", encodeParam(compressId));
}
}));
page.setSkip(true);
} else if (url.contains("getData")) {
List<Selectable> nodes = page.getHtml().$("#VehicleInfoModel_modelShowArea table").nodes();
Map<String, List<Oil>> map = Maps.newHashMapWithExpectedSize(nodes.size() / 2);
StringBuilder stringBuilder = null;
for (int i = 0; i < nodes.size(); i++) {
if (i % 2 == 0) {
stringBuilder = new StringBuilder();
Joiner.on(";").appendTo(stringBuilder, nodes.get(i).$("td", "text").all());
} else {
Selectable node = nodes.get(i);
Selectable titleNode = node.$("td[rowSpan]");
Selectable oilsNode = node.$("td[lang]");
List<String> rowSpanList = titleNode.$("td", "rowSpan").all();
List<String> titleNameList = titleNode.$("td", "text").all();
List<String> oilNameList = oilsNode.$("span", "text").all();
List<String> oilCapList = oilsNode.$("td", "text").all();
List<Oil> oilList = Lists.newArrayListWithExpectedSize(oilNameList.size());
int current = 0;
for (int j = 0; j < titleNameList.size(); j++) {
String title = titleNameList.get(j);
int row = Integer.parseInt(rowSpanList.get(j));
for (int k = 0; k < row; k++) {
Oil oil = new Oil();
oil.setCat(title);
oil.setCap(oilCapList.get(current));
oil.setName(oilNameList.get(current));
oil.set("cap", oil.getCap()).set("car", stringBuilder.toString()).set("cat", oil.getCat()).set("name", oil.getName());
oilList.add(oil);
current++;
}
}
map.put(stringBuilder.toString(), oilList);
/* List<Selectable> productNames = node.$("span", "text").nodes();
page.addTargetRequests(Lists.transform(productNames, new Function<Selectable, String>() {
@Override
public String apply(Selectable selectable) {
return dataDetailAddressUrl.replace("#PRODUECTNAME", encodeParam(selectable.get()));
}
}));*/
}
}
page.putField("oils", map);
} else if (url.contains("gotoDetailPage")) {
page.putField("value", dataDetailUrl.replace("#PRODUCTADDRESS", encodeParam(page.getRawText())));
}
}
private String encodeParam(String param) {
try {
return URLEncoder.encode(URLEncoder.encode(param, "UTF-8"), "UTF-8");
} catch (UnsupportedEncodingException e) {
return null;
}
}
public Site getSite() {
return site;
}
public static void main(String[] args) {
HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("127.0.0.1", 8087)));
DruidPlugin druidPlugin = new DruidPlugin("jdbc:mysql://192.168.1.7:3306/oil", "root", "root");
ActiveRecordPlugin activeRecordPlugin = new ActiveRecordPlugin(druidPlugin);
activeRecordPlugin.addMapping("oil", Oil.class);
activeRecordPlugin.setDialect(new MysqlDialect());
activeRecordPlugin.setContainerFactory(new CaseInsensitiveContainerFactory());
druidPlugin.start();
activeRecordPlugin.start();
Spider.create(new GitHubSpider())
.addUrl(brandUrl)
.setDownloader(httpClientDownloader)
.addPipeline(new MysqlPipeline())
.setScheduler(new RedisScheduler("127.0.0.1"))
//開啟5個執行緒抓取
.thread(10)
//啟動爬蟲
.run();
}
複製程式碼
public class MysqlPipeline implements Pipeline {
@Override
public void process(ResultItems resultItems, Task task) {
Map<String, List<Oil>> oils = resultItems.get("oils");
if (oils != null && !oils.isEmpty()) {
for (Map.Entry<String, List<Oil>> entry : oils.entrySet()) {
Db.batchSave(entry.getValue(),entry.getValue().size());
}
}
}
}
複製程式碼
public class Oil extends Model<Oil> {
private String name;
private String cap;
private String cat;
public static final Oil dao = new Oil().dao();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCap() {
return cap;
}
public void setCap(String cap) {
this.cap = cap;
}
public String getCat() {
return cat;
}
public void setCat(String cat) {
this.cat = cat;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Oil{");
sb.append("name='").append(name).append('\'');
sb.append(", cap='").append(cap).append('\'');
sb.append(", cat='").append(cat).append('\'');
sb.append('}');
return sb.toString();
}
}
複製程式碼
輕鬆加愉快~當然python也很好用~~~
PS: 我是好孩子……