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()