[Python爬蟲]煎蛋網OOXX妹子圖爬蟲(1)——解密圖片地址


之前在魚C論壇的時候,看到很多人都在用Python寫爬蟲爬煎蛋網的妹子圖,當時我也寫過,爬了很多的妹子圖片。后來煎蛋網把妹子圖的網頁改進了,對圖片的地址進行了加密,所以論壇里面的人經常有人問怎么請求的頁面沒有鏈接。這篇文章就來說一下煎蛋網OOXX妹子圖的鏈接獲取方式。

首先說明一下,之前煎蛋網之所以增加了反爬蟲機制,應該就是因為有太多的人去爬他們的網站了。爬蟲頻繁的訪問網站會給網站帶來壓力,所以,建議大家寫爬蟲簡單的運行成功就適可而止,不要過分地去爬別人的東西。

爬蟲思路分析

圖片下載流程圖

首先,用一張簡單的流程圖(非規范流程圖格式)來展示一下爬取簡單網的妹子圖的整個流程:

煎蛋網爬蟲流程圖

流程圖解讀

1、爬取煎蛋網的妹子圖,我們首先要打開任意一個妹子圖的頁面,比如 http://jandan.net/ooxx/page-44#comments 然后,我們需要請求這個頁面,獲取2個關鍵的信息(后續會說明信息的具體作用),其中第一個信息是每個妹子圖片的 hash 值,這個是后續用來解密生成圖片地址的關鍵信息。

2、在頁面中除了提取到圖片的 hash 之外,還有提取到當前頁的一個關鍵的js文件的地址,這個js文件中包含了一個同樣是用來生成圖片地址的關鍵參數,要得到這個參數,必須去請求這個JS地址,當時妹子圖的每個頁面的js地址是不同的,所以需要從頁面中提取。

3、得到了圖片的 hash 和 js 中的關鍵參數之后,可以根據js 中提供的解密方式,得到圖片的鏈接,這個解密方式后續用Python代碼和js代碼的參照來說明。

4、有了圖片鏈接,下載圖片就不多說了,后續會有第二篇文章,來使用多線程+多進程的方式下載圖片。

頁面分析

網頁源代碼解讀

我們可以打開一個妹子圖的頁面,還是最開始的 http://jandan.net/ooxx/page-44#comments 為例,然后查看源代碼(注意,不是審查元素),可以看到本應該放圖片地址的地方並沒有圖片地址,而是類似於下面的代碼:

<p><img src="//img.jandan.net/img/blank.gif" onload="jandan_load_img(this)" /><span class="img-hash">ece8ozWUT/VGGxW1hlbITPgE0XMZ9Y/yWpCi5Rz5F/h2uSWgxwV6IQl6DAeuFiT9mH2ep3CETLlpwyD+kU0YHpsHPLnY6LMHyIQo6sTu9/UdY5k+Vjt3EQ</span></p>

從這個代碼可以看出來,圖片地址被一個js函數代替了,也就是說圖片地址是由這個jandan_load_img(this)函數來獲取並加載的,所以,現在的關鍵是,需要到JS文件中查找這個函數的意義。

js文件解讀

通過在每個js文件中搜索jandan_load_img,最后可以在一個地址類似於 http://cdn.jandan.net/static/min/1d694f08895d377af4835a24f06090d0.29100001.js 的文件中找到這個函數的定義,將壓縮的JS代碼格式化查看,可以看到具體的定義如下片段:

function jandan_load_img(b) {
    var d = $(b);
    var f = d.next("span.img-hash");
    var e = f.text();
    f.remove();
    var c = f_Qa8je29JONvWCrmeT1AJocgAtaiNWkcN(e, "agC37Is2vpAYzkFI9WVObFDN5bcFn1Px");

這段代碼的意思很容易看懂,首先它提取了當前標簽下css為img-hash的span標簽的文本,也就是我們最開始說的圖片的 hash 值,然后把這個值和一個字符串參數(每個頁面的這個參數是變動的,這個頁面是 agC37Is2vpAYzkFI9WVObFDN5bcFn1Px)一起傳遞到另外一個函數f_Qa8je29JONvWCrmeT1AJocgAtaiNWkcN中,所以我們還要去查看這個函數的意義才行,這個函數就是用來生成圖片鏈接的函數了。

f_ 函數的解讀

可以在js中查找這個f_函數的定義,可以看到有兩個,但是沒關系,根據代碼從上到下執行的規律,我們只需要看比較靠后的那個就行了,完整的內容如下:

var f_Qa8je29JONvWCrmeT1AJocgAtaiNWkcN = function(m, r, d) {
    var e = "DECODE";
    var r = r ? r : "";
    var d = d ? d : 0;
    var q = 4;
    r = md5(r);
    var o = md5(r.substr(0, 16));
    var n = md5(r.substr(16, 16));
    if (q) { if (e == "DECODE") { var l = m.substr(0, q) } } else { var l = "" }
    var c = o + md5(o + l);
    var k;
    if (e == "DECODE") {
        m = m.substr(q);
        k = base64_decode(m)
    }
    var h = new Array(256);
    for (var g = 0; g < 256; g++) { h[g] = g }
    var b = new Array();
    for (var g = 0; g < 256; g++) { b[g] = c.charCodeAt(g % c.length) }
    for (var f = g = 0; g < 256; g++) {
        f = (f + h[g] + b[g]) % 256;
        tmp = h[g];
        h[g] = h[f];
        h[f] = tmp
    }
    var t = "";
    k = k.split("");
    for (var p = f = g = 0; g < k.length; g++) {
        p = (p + 1) % 256;
        f = (f + h[p]) % 256;
        tmp = h[p];
        h[p] = h[f];
        h[f] = tmp;
        t += chr(ord(k[g]) ^ (h[(h[p] + h[f]) % 256]))
    }
    if (e == "DECODE") { if ((t.substr(0, 10) == 0 || t.substr(0, 10) - time() > 0) && t.substr(10, 16) == md5(t.substr(26) + n).substr(0, 16)) { t = t.substr(26) } else { t = "" } }
    return t
};

這個函數需要傳遞3個參數,第一個參數是圖片的 hash值,第二個參數就是在jandan_load_img函數中看到的一個字符串,第三個參數其實沒用,因為在jandan_load_img函數中根本沒有傳入。我們只需要按照JS代碼的意思把這個函數改寫成 Python 代碼就行了。

Python改寫函數

使用Python將f_函數改寫之后應該是這樣的:

def get_imgurl(m, r='', d=0):
    '''解密獲取圖片鏈接'''
    e = "DECODE"
    q = 4
    r = _md5(r)
    o = _md5(r[0:0 + 16])
    n = _md5(r[16:16 + 16])
    l = m[0:q]
    c = o + _md5(o + l)
    m = m[q:]
    k = _base64_decode(m)
    h = list(range(256))
    b = [ord(c[g % len(c)]) for g in range(256)]

    f = 0
    for g in range(0, 256):
        f = (f + h[g] + b[g]) % 256
        tmp = h[g]
        h[g] = h[f]
        h[f] = tmp

    t = ""
    p, f = 0, 0
    for g in range(0, len(k)):
        p = (p + 1) % 256
        f = (f + h[p]) % 256
        tmp = h[p]
        h[p] = h[f]
        h[f] = tmp
        t += chr(k[g] ^ (h[(h[p] + h[f]) % 256]))
    t = t[26:]
    return t

這個函數需要用到另外兩個函數,第一個是MD5加密的函數,這個函數對應的是JS中這樣的段落:

var o = md5(r.substr(0, 16));

js的substr()函數其實就是Python里面的切片的用法,稍微查看一下定義就能懂,不解釋。

MD5加密轉化成Python版本如下:

def _md5(value):
    '''md5加密'''
    m = hashlib.md5()
    m.update(value.encode('utf-8'))
    return m.hexdigest()

然后還有一個bash64的解碼函數,這個函數在js中的這一個段用到了:

k = base64_decode(m)

使用Python的時候需要注意,如果直接使用Python的base64.b64decode的話會報錯,具體的報錯內容是:

binascii.Error: Incorrect padding

所以在將數據進行解碼之前先要處理一下,具體的函數是:

def _base64_decode(data):
    '''bash64解碼,要注意原字符串長度報錯問題'''
    missing_padding = 4 - len(data) % 4
    if missing_padding:
        data += '=' * missing_padding
    return base64.b64decode(data)

到這里,獲取圖片鏈接的函數就完成了,主要就是使用3個函數。

我們可以傳入兩個從網頁中復制到的參數到這個函數中測試一下:

m = 'ece8ozWUT/VGGxW1hlbITPgE0XMZ9Y/yWpCi5Rz5F/h2uSWgxwV6IQl6DAeuFiT9mH2ep3CETLlpwyD+kU0YHpsHPLnY6LMHyIQo6sTu9/UdY5k+Vjt3EQ'
r = 'HpRB2OSft5RhlSyZaXV8xYpvEAgDThcA'
print(get_imgurl(m,r))

可以看到如下輸出:

//ww3.sinaimg.cn/mw600/0073ob6Pgy1fpet9wku7dj30hs0qljuz.jpg

注意:這里的r參數是從每個頁面中的js中復制的,每個頁面的js地址是變動的,這個參數也是變動的。

獲取hash和js地址

之前說過,hash值是獲取圖片地址的關鍵參數,而另外的參數在js文件中,並且這個js文件每個頁面不同,所以現在來提取這兩個關鍵參數。

批量獲取hash

獲取圖片的hash值很方便,我們可以使用 BeautifulSoup 的方法即可,具體的代碼片段:

def get_urls(url):
    '''獲取一個頁面的所有圖片的鏈接'''
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
        'Host': 'jandan.net'
    }
    html = requests.get(url, headers=headers).text
    js_url = 'http:' + re.findall('<script src="(//cdn.jandan.net/static/min/[\w\d]+\.\d+\.js)"></script>', html)[-1]
    _r = get_r(js_url)
    soup = BeautifulSoup(html, 'lxml')
    tags = soup.select('.img-hash')
    for tag in tags:
        img_hash = tag.text
        img_url = get_imgurl(img_hash,_r)
        print(img_url)

提取圖片hash 的代碼是這一句:

soup = BeautifulSoup(html, 'lxml')
    tags = soup.select('.img-hash')
    for tag in tags:
        img_hash = tag.text

獲取js中關鍵字符串

而獲取js地址的方式是使用的正則表達式:

js_url = 'http:' + re.findall('<script src="(//cdn.jandan.net/static/min/[\w\d]+\.\d+\.js)"></script>', html)[-1]

這里要注意,因為正則提取的是一個列表,所以最后需要取列表中的一個鏈接,經過查看,我發現有的頁面有兩個這種JS文件,有一個是被注釋掉了,所以都要使用最后一個,這個的表達方式是列表索引中使用[-1]取最后一個。

得到js地址之后需要請求,然后找到關鍵字符串,具體可以寫成一個函數:

def get_r(js_url):
    '''獲取關鍵字符串'''
    js = requests.get(js_url).text
    _r = re.findall('c=f_[\w\d]+\(e,"(.*?)"\)', js)[0]
    return _r

完整代碼

下面就是獲取一個頁面的全部的圖片鏈接的完整代碼:

# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
import hashlib
import re
import base64


def _md5(value):
    '''md5加密'''
    m = hashlib.md5()
    m.update(value.encode('utf-8'))
    return m.hexdigest()


def _base64_decode(data):
    '''bash64解碼,要注意原字符串長度報錯問題'''
    missing_padding = 4 - len(data) % 4
    if missing_padding:
        data += '=' * missing_padding
    return base64.b64decode(data)


def get_imgurl(m, r='', d=0):
    '''解密獲取圖片鏈接'''
    e = "DECODE"
    q = 4
    r = _md5(r)
    o = _md5(r[0:0 + 16])
    n = _md5(r[16:16 + 16])
    l = m[0:q]
    c = o + _md5(o + l)
    m = m[q:]
    k = _base64_decode(m)
    h = list(range(256))
    b = [ord(c[g % len(c)]) for g in range(256)]

    f = 0
    for g in range(0, 256):
        f = (f + h[g] + b[g]) % 256
        tmp = h[g]
        h[g] = h[f]
        h[f] = tmp

    t = ""
    p, f = 0, 0
    for g in range(0, len(k)):
        p = (p + 1) % 256
        f = (f + h[p]) % 256
        tmp = h[p]
        h[p] = h[f]
        h[f] = tmp
        t += chr(k[g] ^ (h[(h[p] + h[f]) % 256]))
    t = t[26:]
    return t


def get_r(js_url):
    '''獲取關鍵字符串'''
    js = requests.get(js_url).text
    _r = re.findall('c=f_[\w\d]+\(e,"(.*?)"\)', js)[0]
    return _r


def get_urls(url):
    '''獲取一個頁面的所有圖片的鏈接'''
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
        'Host': 'jandan.net'
    }
    html = requests.get(url, headers=headers).text
    js_url = 'http:' + re.findall('<script src="(//cdn.jandan.net/static/min/[\w\d]+\.\d+\.js)"></script>', html)[-1]
    _r = get_r(js_url)
    soup = BeautifulSoup(html, 'lxml')
    tags = soup.select('.img-hash')
    for tag in tags:
        img_hash = tag.text
        img_url = get_imgurl(img_hash,_r)
        print(img_url)


if __name__ == '__main__':
    get_urls('http://jandan.net/ooxx/page-44')

運行上面的代碼,可以打印出這個頁面的所有圖片鏈接,部分鏈接如下:

//ww3.sinaimg.cn/mw600/0073ob6Pgy1fpet9wku7dj30hs0qljuz.jpg
//ww3.sinaimg.cn/mw600/0073tLPGgy1fpet9mszjwj30hs0g1jsv.jpg
//ww3.sinaimg.cn/mw600/0073ob6Pgy1fpesskkgobj31jk1jkk5b.jpg
//wx3.sinaimg.cn/mw600/006XfbArly1fpesq2jn1vj30j60svaz3.jpg
//wx3.sinaimg.cn/mw600/6967abd2gy1fpenoyobrcj20u03d0b2d.jpg
//wx3.sinaimg.cn/mw600/6967abd2gy1fpenp38v9uj20u03zkhdy.jpg

總結:到這里為止,提取煎蛋網妹子圖的圖片鏈接的方式其實已經給出來了,下一篇會接着講通過多線程+多進程的方式下載圖片。

文章收發於http://www.seoerzone.com/article/jiandan-spider/ 轉載請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM