看過我的在線小說播放器博文的朋友問我,能不能詳細介紹一下小說播放鏈接的獲取。本篇博文將要介紹解密有聲小說反爬,重點在於獲得小說真實播放地址。
一.目標
1.首頁
這是一個可以在線播放有聲小說的網站,通過選擇書籍,選擇劇集最后實現有聲小說的在線收聽。
2.網頁源代碼
通過查看網頁源代碼,發現此網站為靜態網站,所有網頁內容都能在源代碼中找到。
二.爬取詳情頁
1.查看詳情頁
可以看到,網頁從上到下大致分為三部分,小說詳情,小說簡介,播放列表。
2.小說詳情
打開開發者工具,摁下鍵盤組合鍵Ctrl+Shift+M,使用鼠標點擊小說詳情確定元素所在html標簽,可以確定,小說詳情在第一個class為book的div標簽里。在這個標簽中能得到小說封面、名稱、類型、等級、狀態、更新時間。
3.小說簡介
在第二個class為book的div標簽中能得到小說簡介、作者、播音。
4.播放列表
在id為playlist的div標簽中,能得到小說的播放列表,每集小說都在對應的li標簽中,li標簽下的a標簽中包含小說劇集和播放網頁地址(並非真正音頻地址)。
三.爬取小說音頻
1.確定數據加載方式
隨便點擊一個劇集,網頁就會跳轉到音頻播放頁面。
使用Ctrl+U查看網頁源代碼,未發現類似.mp3、.m4a格式音頻地址,此時可以確定真實音頻地址被加密了,或者是通過單獨的接口異步加載進入網頁。
2.尋找真實音頻播放地址
開發者模式別關,刷新網頁,點擊網頁的播放鍵,開始播放音頻,將開發者工具篩選從All(所有)改成Media(媒體)。
通過篩選,發現此音頻真實播放地址為:
3.URL解碼
上面的地址是什么哦,好亂啊,不要着急,這是URL編碼,可以使用在線工具進行編碼轉換。
哦,原來網頁將中文進行了編碼轉化。(我用的URL解碼網站:http://www.jsons.cn/urlencode/)。
4.加密方式
回到網頁源代碼,下面這串Js吸引了我的注意。
於是去開發者工具中進行搜索函數名:FonHen_JieMa
發現此函數先是將傳入的參數進行了字符串切割,然后遍歷切割后的數組,使用String.fromCharCode()函數進行處理后,返回結果。
因為對Js了解不多,特地查了一下:
JavaScript fromCharCode()方法:
將Unicode 編碼轉為一個字符
var n = String.fromCharCode(65);
輸出結果:A
此函數會將一個ASCII(Unicode)編碼轉成字符。
5.解密
將加密字符以*為分隔符進行切割,得到:
['', '51', '48', '49', '51', '48', '47', '121', '111', '117', '115', '104', '101', '110', '103', '47', '29572', '24187', '22855', '24187', '47', '26007', '32599', '22823', '-27066', '51', '-24679', '29579', '20256', '-29708', '95', '-29346', '25196', '47', '48', '48', '48', '49', '46', '109', '112', '51', '38', '57', '53', '53', '38', '116', '99']
去除掉空字符串,將數字輸入到ASCII編碼轉換網站上,進行驗證。
驗證了前三位,再隨機選取幾個有符號數輸入進行驗證:
這里解釋一下,為什么會有“負數”,此負數為有符號數,需要轉化成原碼然后進行還原:
對應的Python代碼為:
chr((int(~int(s.replace("-", '')) & 0xffff) + 1))
非有符號數可以直接使用
chr(int(s))
直接獲取對應的 ASCII 字符。
原碼,補碼和反碼的知識可以參考:
四.代碼思路
針對加密參數,提出我的撰寫代碼思路。
五.源代碼
Tingshubao_Spider.py
import requests
import re
from urllib.parse import urljoin
import urllib3
from lxml import etree
urllib3.disable_warnings()#解決warning
class Tingshu_bao_spider:
def do_get_request(self,url):
"""
發送網絡請求,獲取網頁源代碼
:param url:
:return:
"""
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36",
"Referer":url}
try:
r=requests.get(url,headers=headers,timeout=6)
if r.status_code==200:
r.encoding=r.apparent_encoding
html=r.text
return html
else:
return False
except:
return False
def get_novel_detail(self,sound_link):
"""
獲取小說詳情
:param sound_link:
:return:
"""
novel_detail_item={}
html=self.do_get_request(sound_link)
if html:
res=etree.HTML(html)
name=res.xpath('//div[@class="book-cell"]/h1[@class="book-title"]/text()')
if name:
novel_detail_item['novel_name']=name[0].split("有聲小說簡介:")[0]
else:
novel_detail_item['novel_name']="未知"
cover=res.xpath('//div[@class="book"]/img[@class="book-cover"]/@src')
if cover:
novel_detail_item['novel_cover']=urljoin(sound_link,cover[0])
else:
novel_detail_item['novel_cover']="未知"
datas=res.xpath('//div[@class="book-rand-a"]//text()')
if datas:
novel_detail_item['novel_type'] = datas[1]
novel_detail_item['novel_status'] = datas[3]
novel_detail_item['novel_update_time'] = datas[-1]
else:
novel_detail_item['novel_type']="未知"
novel_detail_item['novel_status'] = "未知"
novel_detail_item['novel_update_time'] = "未知"
#作者
data2 = res.xpath('//div[@class="book-des"]/p/a/text()')
if data2:
novel_detail_item['novel_author'] = data2[0]
novel_detail_item['novel_anchor'] = data2[-1]
else:
novel_detail_item['novel_author']="未知"
novel_detail_item['novel_anchor']="未知"
introduce = res.xpath('//div[@class="book-des"]/text()')
if introduce:
novel_detail_item['novel_introduce'] = introduce[0]
else:
novel_detail_item['novel_introduce']="未知"
selector=res.xpath('//div[@id="playlist"]/ul/li')
play_list=[]
for data in selector:
play_item={}
novel_play_name=data.xpath("./a/@title")
if novel_play_name:
play_item["play_name"]=novel_play_name[0]
else:
play_item["play_name"]="NULL"
novel_play_link = data.xpath("./a/@href")
if novel_play_name:
play_item["play_link"] = urljoin(sound_link,novel_play_link[0])
else:
play_item["play_link"]="NULL"
play_list.append(play_item)
novel_detail_item['play_list']=play_list
return novel_detail_item
else:
return False
def get_audio_play_link(self,detail_intro_link):
"""
獲取小說播放鏈接地址
:param detail_intro_link:
:return:
"""
html=self.do_get_request(detail_intro_link)
if html:
base_url="https://t3344t.tingchina.com/"
aim_asciis=re.findall("FonHen_JieMa\('(.*?)'",html)
if aim_asciis:
sp = aim_asciis[0].split("*")
res = ""
for s in sp:
if s != "":
if "-" in s:
res += chr((int(~int(s.replace("-", '')) & 0xffff) + 1))
else:
res += chr(int(s))
aim_suffix = "/" + res.split('&')[0].split('/', 1)[-1]
play_url=urljoin(base_url,aim_suffix)
return play_url
else:
return False
else:
return False
if __name__ == '__main__':
t=Tingshu_bao_spider()
aim_url='http://m.tingshubao.com/book/2267.html'
print(t.get_novel_detail(aim_url))
print(t.get_audio_play_link('http://m.tingshubao.com/video/?2267-0-0.html'))
六.結果
1.詳情頁
2.音頻播放地址
有了真實播放地址,就能寫代碼,下載音頻了。
七.總結
本次分析了一個有聲小說網站,重點在於分析其小說詳情頁、音頻播放地址,加密方式判斷。思路、代碼方面有什么不足歡迎各位大佬指正、批評!