JS逆向:狐妖小红娘漫画扒取

url: http://www.chuixue.net/manhua/12533/

工具: Pycharm, Chrome, node.js

最近在学python, 接触了点爬虫的相关知识, 学以致用, 所以随手找了本漫画《狐妖小红娘》来练手。本来以为漫画网站都挺简单的,因为都是小网站, 应该不会有什么反爬机制。然而...

误区一: 小网站可能直接利用的别人的网站架构, 里面可能也有反爬的坑。就算没有运维IP检测, 想混进去也不容易。

误区二: 反爬的主要手段出了检测IP,请求头之类的, 还有一大杀器:前端JS, 因为大部分学python的对JS了解不是很多。

对于漫画网站来讲, 一张图片大小从200k到2M不等, 如果直接加载,那么我们看到的效果可能是一打开网站, 半天都是空白, 然

后刷出整个网站, 这样是非常影响体验的, 所以大部分漫画网站都采用了JS动态加载。

       而针对狐妖小红娘这部漫画,它里面估计是后端分几次写的, 所以主域名都有两个, 而且前半部分的漫画和后半部分的前端文档结构不一致。下面上干货:

首先通过上面的url获取首页里面的各章节文档, 这个难度不大, 直接requests获取就OK了。

进入到下一层页面后首先一个注意点是:

我们通过requests获取到的页面都是在Sources里面的, Elements的都是本地浏览器加载JS后动态渲染后的文档结构。而打开Sources里面的文件一看, 可以发现他里面大部分都是JS代码混在一些html标签里面, 图片地址和下一页的链接都没有, 但是里面有个东西大家应该很熟悉

和加载后的文档对比就会发现这是图片的url, so,初步完成, 只要拼接url就OK了...

但是, 爬了几十章之后就断了, 再看看文档结构, 可以发现里面的图片url之前的域名居然换了!!!

检查下js会发现里面有这么个玩意

再修改下代码, 继续爬取, 可是过了几十章,又不行了, 在看看文档结构, 没什么问题啊, 可是仔细找的话会发现静态文件变了。

原来的photosr赋值没有了, 取而代之的时这么一串乱七八糟的字符串,而查查network,好像也没什么有用的ajax请求,好像不是通过

ajax动态请求图片的, 可以仔细看看这串字符串, 好像有的是以‘==’结尾的,base64解码以下, 可以获取下面这串字符串

好像是个js代码字符串啊, 把它复制到一个js文件里运行以下(需要node.js), 结果发现它报错了,说是有啥变量没有定义,很好理

解, 复制过来的代码嘛, photosr没有预定义,a这个数组也没有预定义, 那我加进去定义下呗? 结果又提示p未定义, 但是有个惊

喜是里面的return p之前是可以打印出p的, 就是我们要的图片数组...

仔细看看这串JS代码, 可以发现, 他是把需要解析的url放在了一串字符串中, 然后利用js动态从里面提取出来, 前面的a[x] =

'a/b/c/d/e.f'就是提取规则, 后面以|分隔的就是要提取的字符串, 但是a的下标里面还有p和n这个代表什么呢?研究下js方法里面的实

现就会发现, 上面传的参数里面有两个数字, 28, 28, 就是说这个解析规则是按照28进制来设置下标的(其实如果扒完了会发现

最大到了62进制), 而python最大支持的int(‘x’, base=''),只到36进制, 这就需要我们自己去写相关的python实现了, 也就是我们

需要利用python去实现这段代码解析的过程, 然后动态解析出url数组,之后再利用最开始的url拼接规则就ok啦。

其实对应最后一步, 如果不是进制原因的话, 是可以利用execjs包来在python中执行js代码的, 这样的话就可以直接获取里面的js返

回值, 但是因为进制的坑, 所以这里用到了js转python代码。

相关代码实现如下:

import os
import re
import sys
from time import sleep
import base64
from threading import Thread
import queue

import requests
from lxml import etree

q = queue.Queue(maxsize=5)


sourceurl = "http://www.chuixue.net"
baseurl = "http://www.chuixue.net/manhua/12533/"
headers = {
    'User-Agent': 'Mozilla/5.0'
}


def getImg(url, chapter, surl, num):
    try:
        headers['HOST'] = surl.split('/')[2]
        data = requests.get(url, headers=headers)
        if not os.path.exists(chapter):
            os.mkdir(chapter)
        with open(chapter + '/' + str(num) + '.' + url.split('.')[-1], 'wb') as f:
            f.write(data.content)
    except Exception as e:
        print('图片下载错误:', url, chapter, surl)

def getIndex(u):
    if ord('0') <= ord(u) <= ord('9'):
        index = int(u)
    elif ord('a') <= ord(u) <= ord('z'):
        index = ord(u) - ord('a') + 9 + 1
    else:
        index = ord(u) - ord('A') + 35 + 1
    return index

def decodeBase(pack):
    bbytes = base64.decodebytes(pack.encode())
    params = re.findall('}\((.*?)\)\)', bbytes.decode(), re.S)[0].split(',')
    print(params)
    jz = int(params[1])
    print(jz)
    totalD = []
    rule = re.findall('\w\[\d\]="(\S+?)"', params[0], re.S)
    ti = 0
    for ru in rule:
        st = params[3].strip("'").split('.')[0].strip("'").split('|')
        rend = ru.split('/').pop(-1).split('.')
        r = ru.split('/')[:-1] + rend
        data = []
        for u in r:
            index = -1
            if len(u) == 1:
                index = getIndex(u)
            else:
                b = 0
                for i in range(len(u)):
                    b += getIndex(u[i]) * (62 ** (len(u) - i - 1))
                index = b
            data.append(st[index] if st[index] else u)
        f = data.pop(-1)
        data = '/'.join(data) + '.' + f
        totalD.append((ti, data))
        ti += 1
    return totalD



def getPage(url):
    print(url)
    headers['HOST'] = 'www.chuixue.net'
    try:
        req = requests.get(url, headers=headers)
        req.encoding = 'gb18030'
        # print(req.text)
        html = etree.HTML(req.text)
        script = html.xpath('//script[1]/text()')[0]
        title = html.xpath('//h1[1]/text()')[0]
        data = re.findall(r'.*?photosr\[(\w+)\]\s="(\S+)".*?', script, flags=re.S)
        if not data:
            pack = re.findall(r'packed="(\S+?)"', script, flags=re.S)[0]
            data = decodeBase(pack)
        print(data)
        count = len(data)
        chapter = url.split('/')[-1].split('.')[0]
        surl = ""
        if int(chapter) > 519253:
            surl = 'http://2.csc1998.com/'
        else:
            surl = 'http://img.csc1998.com/'
        num = 0
        for i in data:
            imgurl = surl + i[1]
            print(imgurl, surl)
            getImg(imgurl, title, surl, num)
            num += 1
            sleep(0.5)
    except:
        sleep(2)
        getPage(url)
        print('下载错误,请稍等会...', url)



req = requests.get(baseurl, headers=headers)
html = etree.HTML(req.text)
pageurl = html.xpath('//div[@class="plist pmedium max-h200"]/ul/li/a/@href')
p = input('请输入页码:')
if not p.isdigit():
    sys.exit(-1)
p = int(p)
pageurl = pageurl[:-p]

def pushURL():
    while pageurl:
        page = pageurl.pop()
        url = sourceurl + page
        q.put(url)


def getURL():
    while True:
        sleep(1)
        if not q.empty():
            url = q.get()
            getPage(url)

tlist = []
for i in range(3):
    thread = Thread(target=pushURL)
    tlist.append(thread)
    thread.start()

for i in range(3):
    thread = Thread(target=getURL)
    tlist.append(thread)
    thread.start()

for i in range(len(tlist)):
    tlist[i].join()

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值