Python爬虫爬取煎蛋网无聊图

目标

爬取无聊图板块下所有图片和gif

难点

以前煎蛋网是没有难度的,数据都明文写在网页源码里,但是因为爬的人太多,所以做了一些反爬措施,主要是将真实的图片地址用Base64加密了。也希望观看这篇文章的人设计友好爬虫,不要给网站增加太多负担。

做法

一:从网页源码中找到数据位置

我用的是Chrome浏览器,打开网页后按下F12进入开发者工具,在网页中找到你想要的数据,然后通过开发者工具左上角的箭头中的来选中


Python爬虫爬取煎蛋网无聊图_第1张图片
image.png

二:从网页源码中获取有效信息

获取网页源码可以通过python的requests库或者urllib,甚至你可以用aiohttp来实现异步获取提高性能,这里我用的是requests。

  url = 'http://jandan.net/pic'
  print(requests.get(url).text)

通过打印得到的源码可以看到应该出现图片地址的地方变成了
Ly93eDIuc2luYWltZy5jbi9tdzY5MC8wMDcydnZIQ2d5MWZ0MW80NXhwMDBnMzBhOTA1a3UxYS5naWY=


这里煎蛋对图片地址做了加密,如果你熟悉Base64的话,看到img-hash你就可以合理的猜测是对地址用了Base64编码。

三:解决网址加密问题

从源码的onload="jandan_load_img(this)"入手,全局查找jandan_load_img函数,发现函数长这样:

Python爬虫爬取煎蛋网无聊图_第2张图片
image.png

可以发现关键的地方在于 var c = jdeSJ67kTPs5IJjmfYHUx7fAWBOhNRGF5V(e, "HWnYZD8ysL1ZI1HaZU7UbZ29tw08jSr0");
继续全局找这个函数:

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这个库可以很方便的对数据进行Base64编码和解码
随意对其中一个地址进行解码,发现长这样
b'//wx2.sinaimg.cn/mw690/0072vvHCgy1ft1o45xp00g30a905ku1a.gif'

# 构建正确的图片地址
url = ('http:' + str(base64.b64decode(item.string.encode('utf-8')))[2:]).replace('\'', '')

四:下载数据到本地

这部分就比较简单了,代码如下

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 是我用来做多进程循环爬取时退出的一个标记
其中BeautifulSoup的使用可以直接查看官网中文文档,除了它之外,还有lxml等,可以根据自己需要了解选取。这些库的作用简单来说就是:

通过html代码构造一个结构化的数据,提供API方便对数据处理

所以,你其实可以完全不用这些解析器,直接通过re编写正则表达式来获取信息也是可以的。

最后

学习爬虫原则:学习技术,友好爬取,不要给服务器增加额外负担。
为什么会给服务器增加负担:

服务器可以比作是一个内存等资源较大的个人计算机,就像你同时开很多进程的时候计算机会卡甚至死机一样,服务器同时处理太多的请求也是这样的道理。

所以如果你只是学习技术而不是看重数据的话,最终可以拿到那最终的一份数据就可以了。
如果需要获取数据的话,可以考虑在夜深人少服务器比较空闲的时候进行。
贴下完整代码

# -*- 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)

如果有碰到什么问题,欢迎留言交流

考虑一下,如果要根据OO跟XX数来抓取的话,要怎么做

你可能感兴趣的:(Python爬虫爬取煎蛋网无聊图)