目標(biāo)
爬取無聊圖板塊下所有圖片和gif
難點
以前煎蛋網(wǎng)是沒有難度的,數(shù)據(jù)都明文寫在網(wǎng)頁源碼里直晨,但是因為爬的人太多,所以做了一些反爬措施,主要是將真實的圖片地址用Base64加密了跟束。也希望觀看這篇文章的人設(shè)計友好爬蟲,不要給網(wǎng)站增加太多負擔(dān)丑孩。
做法
一:從網(wǎng)頁源碼中找到數(shù)據(jù)位置
我用的是Chrome瀏覽器冀宴,打開網(wǎng)頁后按下F12進入開發(fā)者工具,在網(wǎng)頁中找到你想要的數(shù)據(jù)温学,然后通過開發(fā)者工具左上角的箭頭中的來選中
二:從網(wǎng)頁源碼中獲取有效信息
獲取網(wǎng)頁源碼可以通過python的requests庫或者urllib略贮,甚至你可以用aiohttp來實現(xiàn)異步獲取提高性能,這里我用的是requests仗岖。
url = 'http://jandan.net/pic'
print(requests.get(url).text)
通過打印得到的源碼可以看到應(yīng)該出現(xiàn)圖片地址的地方變成了
<img src="http://img.jandan.net/img/blank.gif" onload="jandan_load_img(this)" /><span class="img-hash">Ly93eDIuc2luYWltZy5jbi9tdzY5MC8wMDcydnZIQ2d5MWZ0MW80NXhwMDBnMzBhOTA1a3UxYS5naWY=</span></p>
這里煎蛋對圖片地址做了加密逃延,如果你熟悉Base64的話,看到img-hash
你就可以合理的猜測是對地址用了Base64編碼轧拄。
三:解決網(wǎng)址加密問題
從源碼的onload="jandan_load_img(this)"
入手真友,全局查找jandan_load_img
函數(shù),發(fā)現(xiàn)函數(shù)長這樣:
可以發(fā)現(xiàn)關(guān)鍵的地方在于
var c = jdeSJ67kTPs5IJjmfYHUx7fAWBOhNRGF5V(e, "HWnYZD8ysL1ZI1HaZU7UbZ29tw08jSr0");
繼續(xù)全局找這個函數(shù):
var jdeSJ67kTPs5IJjmfYHUx7fAWBOhNRGF5V = function(o, y, g) {
var d = o;
var l = "DECODE";
var y = y ? y : "";
var g = g ? g : 0;
var h = 4;
y = md5(y);
var x = md5(y.substr(0, 16));
var v = md5(y.substr(16, 16));
if (h) {
if (l == "DECODE") {
var b = md5(microtime());
var e = b.length - h;
var u = b.substr(e, h)
}
} else {
var u = ""
}
var t = x + md5(x + u);
var n;
if (l == "DECODE") {
g = g ? g + time() : 0;
tmpstr = g.toString();
if (tmpstr.length >= 10) {
o = tmpstr.substr(0, 10) + md5(o + v).substr(0, 16) + o
} else {
var f = 10 - tmpstr.length;
for (var q = 0; q < f; q++) {
tmpstr = "0" + tmpstr
}
o = tmpstr + md5(o + v).substr(0, 16) + o
}
n = o
}
var k = new Array(256);
for (var q = 0; q < 256; q++) {
k[q] = q
}
var r = new Array();
for (var q = 0; q < 256; q++) {
r[q] = t.charCodeAt(q % t.length)
}
for (var p = q = 0; q < 256; q++) {
p = (p + k[q] + r[q]) % 256;
tmp = k[q];
k[q] = k[p];
k[p] = tmp
}
var m = "";
n = n.split("");
for (var w = p = q = 0; q < n.length; q++) {
w = (w + 1) % 256;
p = (p + k[w]) % 256;
tmp = k[w];
k[w] = k[p];
k[p] = tmp;
m += chr(ord(n[q]) ^ (k[(k[w] + k[p]) % 256]))
}
if (l == "DECODE") {
m = base64_encode(m);
var c = new RegExp("=","g");
m = m.replace(c, "");
m = u + m;
m = base64_decode(d)
}
return m
};
代碼的解讀呢紧帕,就是它對加密的圖像地址進行了base64解碼盔然,所以解決思路是直接對源碼中的Base64圖像地址進行解碼桅打。
在python中利用base64這個庫可以很方便的對數(shù)據(jù)進行Base64編碼和解碼
隨意對其中一個地址進行解碼,發(fā)現(xiàn)長這樣
b'//wx2.sinaimg.cn/mw690/0072vvHCgy1ft1o45xp00g30a905ku1a.gif'
# 構(gòu)建正確的圖片地址
url = ('http:' + str(base64.b64decode(item.string.encode('utf-8')))[2:]).replace('\'', '')
四:下載數(shù)據(jù)到本地
這部分就比較簡單了愈案,代碼如下
def download_data(url):
global num
dir_path = os.path.abspath('..')
file_name = url.split('.')[2][-8:-1]
postfix = url.split('.')[-1].replace('\'', '')
with open(dir_path + f'\\jan_dan\\wu_liao_tu\\{file_name}.{postfix}', 'wb') as f:
f.write(requests.get(url, headers=header).content)
print(f'{num} task done')
num += 1
Flag
是我用來做多進程循環(huán)爬取時退出的一個標(biāo)記
其中BeautifulSoup的使用可以直接查看官網(wǎng)中文文檔挺尾,除了它之外,還有l(wèi)xml等站绪,可以根據(jù)自己需要了解選取遭铺。這些庫的作用簡單來說就是:
通過html代碼構(gòu)造一個結(jié)構(gòu)化的數(shù)據(jù),提供API方便對數(shù)據(jù)處理
所以恢准,你其實可以完全不用這些解析器魂挂,直接通過re編寫正則表達式來獲取信息也是可以的。
最后
學(xué)習(xí)爬蟲原則:學(xué)習(xí)技術(shù)馁筐,友好爬取涂召,不要給服務(wù)器增加額外負擔(dān)。
為什么會給服務(wù)器增加負擔(dān):
服務(wù)器可以比作是一個內(nèi)存等資源較大的個人計算機敏沉,就像你同時開很多進程的時候計算機會卡甚至死機一樣果正,服務(wù)器同時處理太多的請求也是這樣的道理。
所以如果你只是學(xué)習(xí)技術(shù)而不是看重數(shù)據(jù)的話盟迟,最終可以拿到那最終的一份數(shù)據(jù)就可以了秋泳。
如果需要獲取數(shù)據(jù)的話,可以考慮在夜深人少服務(wù)器比較空閑的時候進行攒菠。
貼下完整代碼
# -*- coding:utf-8 -*-
# author: 禾斗 2018.7
import requests
from bs4 import BeautifulSoup
import base64
import os
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time, random
start_url = 'http://jandan.net/pic/page-232#comments'
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
}
num = 0
Flag = True
def download_data(url):
global num
dir_path = os.path.abspath('..')
file_name = url.split('.')[2][-8:-1]
postfix = url.split('.')[-1].replace('\'', '')
with open(dir_path + f'\\jan_dan\\wu_liao_tu\\{file_name}.{postfix}', 'wb') as f:
f.write(requests.get(url, headers=header).content)
print(f'{num} task done')
num += 1
def get_wuliaotu(url):
global start_url, Flag
try:
resp = requests.get(url, headers=header)
bs = BeautifulSoup(resp.text, 'html.parser')
next_url = 'http:'+ bs.find('a', class_='previous-comment-page').get('href')
except Exception as err:
print(f'Error:{err}')
Flag = False
return Flag
url_ls = set()
for item in bs.find_all('span', class_='img-hash'):
url = ('http:' + str(base64.b64decode(item.string.encode('utf-8')))[2:]).replace('\'', '')
url_ls.add(url)
pool = ProcessPoolExecutor(max_workers=8)
pool.map(download_data, url_ls)
url_ls.clear()
start_url = next_url
time.sleep(random.randint(3,6))
if __name__ == '__main__':
while True:
get_wuliaotu(start_url)
如果有碰到什么問題迫皱,歡迎留言交流
考慮一下,如果要根據(jù)OO跟XX數(shù)來抓取的話辖众,要怎么做