在動漫之家選擇一本漫畫下載,下載一本章節不是那么多的漫畫吧。《武林之王的退隱生活》
url=https://www.dmzj.com/info/wulinzhiwangdetuiyinshenghuo.html
想下載這本動漫,需要保存所有章節的圖片到本地。先捋捋思路:
-
拿到所有章節名和章節鏈接
-
根據章節鏈接章節里的所有漫畫圖片
-
根據章節名,分類保存漫畫
獲取章節名和章節鏈接
分析一下html
分析可以發現div
標簽下有個ul
標簽,ul
標簽是距離a
標簽最近的標簽。
用上一篇文章講解的BeautifulSoup,實際上直接匹配最近的class
屬性為list_con_li
的ul
標簽即可。代碼如下:
import requests from bs4 import BeautifulSoup target_url='https://www.dmzj.com/info/wulinzhiwangdetuiyinshenghuo.html' r=requests.get(url=target_url) bs=BeautifulSoup(r.text,'lxml') list_con_li=bs.find('ul',class_='list_con_li') comic_list=list_con_li.find_all('a') chapter_names=[] chapter_urls=[] for comic in comic_list: href=comic.get('href') name=comic.text chapter_names.insert(0,name) chapter_urls.insert(0,href) print(chapter_names) print(chapter_urls)
章節名和章節鏈接搞定了!
獲取漫畫圖片地址
我們只要分析在一個章節里怎么獲取圖片,就能批量的在各個章節獲取漫畫圖片。
我們先看第一章的內容。
url:https://www.dmzj.com/view/wulinzhiwangdetuiyinshenghuo/75529.html
打開第一章的鏈接,你會發現,鏈接后面自動添加了#@page=1。
你翻頁會發現,第二頁的鏈接是后面加了#@page=2,第三頁的鏈接是后面加了#@page=3,以此類推。
但是,這些並不是圖片的地址,而是這個展示頁面的地址,要下載圖片,首先要拿到圖片的真實地址。
審查元素找圖片地址,你會發現,這個頁面不能右鍵!
這就是最最最最低級的反爬蟲手段,這個時候我們可以通過鍵盤的F12調出審查元素窗口。
有的網站甚至把F12都禁掉,這種也是很低級的反爬蟲手段。
面對這種禁止看頁面源碼的初級手段,一個優雅的通用解決辦法是,在連接前加個view-source:
。
view-source:https://www.dmzj.com/view/wulinzhiwangdetuiyinshenghuo/75529.html
用這個鏈接,直接看的就是頁面源碼。
更簡單的辦法是,將鼠標焦點放在瀏覽器地址欄,然后按下F12依然可以調出調試窗口。
這個漫畫網站,還是可以通過F12審查元素,調出調試窗口的。
我們可以在瀏覽器調試窗口中的Network里找到這個頁面加載的內容,例如一些css文件啊、js文件啊、圖片啊,等等等。
要找圖片的地址,直接在這里找,別在html頁面里找,html信息那么多,一條一條看得找到猴年馬月。
在Network中可以很輕松地找到我們想要的圖片真實地址,調試工具很強大,Headers可以看一些請求頭信息,Preview可以瀏覽返回信息。
搜索功能,過濾功能等等,應有盡有,具體怎么用,自己動手點一點,就知道了!
好了,拿到了圖片的真實地址,我們看下鏈接:
https://images.dmzj.com/img/chapterpic/24629/94632/1524795145232.jpg
這就是圖片的真實地址,拿着這個鏈接去html頁面中搜索,看下它存放在哪個img
標簽里了,搜索一下你會發現,瀏覽器中的html頁面是有這個圖片鏈接的。
但你是用view-source:
打開下面這個頁面,在這個頁面內你會發現你搜索不到這個圖片鏈接。(在源碼里搜不到圖片鏈接)
view-source:https://www.dmzj.com/view/wulinzhiwangdetuiyinshenghuo/75529.html
記住,這就說明,這個圖片是動態加載的!
使用view-source:
方法,就是看頁面源碼,並不管動態加載的內容。這里面沒有圖片鏈接,就說明圖片是動態加載的。
使用JavaScript動態加載,無外乎兩種方式:
-
外部加載
-
內部加載
外部加載就是在html頁面中,以引用的形式,加載一個js,例如這樣:
<script type="text/javascript" src="https://xxxxxx.com/call.js"></script>
這段代碼得意思是,引用xxxxxx.com域名下的call.js文件。
內部加載就是Javascript腳本內容寫在html內,例如這個漫畫網站。
這時候,就可以用搜索功能了,教一個搜索小技巧。
https://images.dmzj.com/img/chapterpic/24629/94632/1524795145232.jpg
圖片鏈接是這個,那就用圖片的名字去掉后綴,也就是1524795145232在瀏覽器的調試頁面搜索,因為一般這種動態加載,鏈接都是程序合成的,搜它准沒錯!
<script type="text/javascript"> var arr_img = new Array(); var page = ''; eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('o l=\'{"h":"m","j":"0","i":"k\\/5\\/3\\/2\\/g.4\\r\\6\\/5\\/3\\/2\\/p.4\\r\\6\\/5\\/3\\/2\\/f.4\\r\\6\\/5\\/3\\/2\\/a.4\\r\\6\\/5\\/3\\/2\\/8.4\\r\\6\\/5\\/3\\/2\\/7.4\\r\\6\\/5\\/3\\/2\\/9.4\\r\\6\\/5\\/3\\/2\\/b.4\\r\\6\\/5\\/3\\/2\\/e.4\\r\\6\\/5\\/3\\/2\\/d.4\\r\\6\\/5\\/3\\/2\\/c.4\\r\\6\\/5\\/3\\/2\\/n.4\\r\\6\\/5\\/3\\/2\\/u.4\\r\\6\\/5\\/3\\/2\\/F.4\\r\\6\\/5\\/3\\/2\\/q.4\\r\\6\\/5\\/3\\/2\\/C.4\\r\\6\\/5\\/3\\/2\\/B.4\\r\\6\\/5\\/3\\/2\\/E.4\\r\\6\\/5\\/3\\/2\\/G.4\\r\\6\\/5\\/3\\/2\\/D.4\\r\\6\\/5\\/3\\/2\\/z.4\\r\\6\\/5\\/3\\/2\\/A.4\\r\\6\\/5\\/3\\/2\\/t.4","s":"v","w":"1","y":"\\x\\H"}\';',44,44,'||94632|24629|jpg|chapterpic|nimg|15247951474002|1524795146867|15247951476582|15247951465477|15247951477904|15247951485607|15247951484544|15247951481542|1524795146088|1524795145232|id|page_url|hidden|img|pages|75529|15247951487273|var|15247951456304|15247951495716||sum_pages|15247951541883|15247951490839|23|chapter_order|u5e8f|chapter_name|1524795152899|15247951534199|15247951507199|15247951500936|15247951521389|15247951511347|15247951494295|15247951516905|u7ae0'.split('|'),0,{})) </script>
不出意外,你就能看到這段代碼,1524795145232就混在其中!
再分析分析,看看能不能優雅的解決這個動態加載問題,我們再看這個圖片鏈接:
https://images.dmzj.com/img/chapterpic/24629/94632/1524795145232.jpg
這個鏈接就是這幾個數字合成,所以可以把這些數字弄出來,拼出圖片鏈接
import requests from bs4 import BeautifulSoup import re url='https://www.dmzj.com/view/wulinzhiwangdetuiyinshenghuo/75529.html' r=requests.get(url=url) html=BeautifulSoup(r.text,'lxml') script_info=html.script pics=re.findall('\d{13,14}',str(script_info)) chapterpic_hou=re.findall('\|(\d{5})',str(script_info))[0] chapterpic_qian=re.findall('\|(\d{5})',str(script_info))[1] for pic in pics: url='https://images.dmzj.com/img/chapterpic/'+ chapterpic_qian +'/'+ chapterpic_hou+'/'+pic+'.jpg' print(url)
運行代碼,結果如下:
比對一下會發現,這些真就是漫畫圖片的鏈接!
但是有個問題,這么合成的的圖片鏈接不是按照漫畫順序的,一般排序都是小數放在前面,大數放在后面,這些長的數字里,有13位的,有14位的,並且都是以14開頭的數字,所以猜測它是末位補零后的結果,就是圖片的順序!
import requests from bs4 import BeautifulSoup import re url='https://www.dmzj.com/view/wulinzhiwangdetuiyinshenghuo/75529.html' r=requests.get(url=url) html=BeautifulSoup(r.text,'lxml') script_info=html.script pics=re.findall('\d{13,14}',str(script_info)) for idx,pic in enumerate(pics): if len(pic)==13: pics[idx]=pic+'0' pics=sorted(pics,key=lambda x:int(x)) chapterpic_hou=re.findall('\|(\d{5})',str(script_info))[0] chapterpic_qian=re.findall('\|(\d{5})',str(script_info))[1] for pic in pics: if pic[-1]=='0': url='https://images.dmzj.com/img/chapterpic/'+ chapterpic_qian +'/'+ chapterpic_hou+'/'+pic[:-1]+'.jpg' else: url='https://images.dmzj.com/img/chapterpic/'+ chapterpic_qian +'/'+ chapterpic_hou+'/'+pic+'.jpg' print(url)
程序對13位的數字,末位補零,然后排序。
跟網頁的鏈接按順序比對,會發現沒錯!就是這個順序!
下載圖片
使用其中一個圖片鏈接,用代碼下載試試。
import requests from urllib.request import urlretrieve dn_url='https://images.dmzj.com/img/chapterpic/24629/94632/1524795145232.jpg' urlretrieve(dn_url,'1.jpg')
通過urlretrieve
方法,就可以下載,這是最簡單的下載方法。第一個參數是下載鏈接,第二個參數是下載后的文件保存名。
不出意外,就可以順利下載這張圖片!
但是,意外發生了!
出現了HTTP Error,錯誤代碼是403。403表示資源不可用,這是又是一種典型的反扒蟲手段。
打開這個圖片鏈接:
https://images.dmzj.com/img/chapterpic/24629/94632/1524795145232.jpg
這個地址就是圖片的真實地址,在瀏覽器中打開,可能直接無法打開,或者能打開,但是一刷新就又不能打開了!
如果打開章節頁面后,再打開這個圖片鏈接就又能看到圖片了。
章節url:https://www.dmzj.com/view/wulinzhiwangdetuiyinshenghuo/75529.html
記住,這就是一種典型的通過Referer的反扒爬蟲手段!
Referer可以理解為來路,先打開章節URL鏈接,再打開圖片鏈接。打開圖片的時候,Referer的信息里保存的是章節URL。
動漫之家網站的做法就是,站內的用戶訪問這個圖片,我就給他看,從其它地方過來的用戶,我就不給他看。
是不是站內用戶,就是根據Referer進行簡單的判斷。
這就是很典型的,反爬蟲手段!
解決辦法:
import requests from contextlib import closing download_header={ 'Referer':'https://www.dmzj.com/view/wulinzhiwangdetuiyinshenghuo/75529.html' } dn_url='https://images.dmzj.com/img/chapterpic/24629/94632/1524795145232.jpg' with closing(requests.get(dn_url,headers=download_header,stream=True))as response: chunk_size=1024 content_size=int(response.headers['content-length']) if response.status_code==200: print('文件大小:%0.2fKB' %(content_size/chunk_size)) with open('1.jpg','wb') as file: for data in response.iter_content(chunk_size=chunk_size ): file.write(data) else: print('鏈接異常') print('下載完成!')
使用closing
方法可以設置Headers信息,這個Headers信息里保存Referer來路,就是第一章的URL,最后以寫文件的形式,保存這個圖片。
下載完成
漫畫下載
完整代碼:
import requests import os import re from bs4 import BeautifulSoup from contextlib import closing from tqdm import tqdm import time save_dir = '武林之王的退隱生活' if save_dir not in os.listdir('./'): os.mkdir(save_dir) target_url = "https://www.dmzj.com/info/wulinzhiwangdetuiyinshenghuo.html" # 獲取動漫章節鏈接和章節名 r = requests.get(url = target_url) bs = BeautifulSoup(r.text, 'lxml') list_con_li = bs.find('ul', class_="list_con_li") cartoon_list = list_con_li.find_all('a') chapter_names = [] chapter_urls = [] for cartoon in cartoon_list: href = cartoon.get('href') name = cartoon.text chapter_names.insert(0, name) chapter_urls.insert(0, href) for i, url in enumerate(tqdm(chapter_urls)): download_header = { 'Referer': url } name = chapter_names[i] while '.' in name: name = name.replace('.', '') chapter_save_dir = os.path.join(save_dir, name) if name not in os.listdir(save_dir): os.mkdir(chapter_save_dir) r = requests.get(url = url) html = BeautifulSoup(r.text, 'lxml') script_info = html.script pics = re.findall('\d{13,14}', str(script_info)) for j, pic in enumerate(pics): if len(pic) == 13: pics[j] = pic + '0' pics = sorted(pics, key=lambda x:int(x)) chapterpic_hou = re.findall('\|(\d{5,6})', str(script_info))[0] chapterpic_qian = re.findall('\|(\d{5,6})', str(script_info))[1] for idx, pic in enumerate(pics): if pic[-1] == '0': if int(chapterpic_hou) > int(chapterpic_qian):#因為有些章節的這兩個值位置是變化的,所以不能寫死位置,但是不變的肯定是小的數值在前面,所以這做一下比較就可以了 url = 'https://images.dmzj.com/img/chapterpic/' +chapterpic_qian + '/' +chapterpic_hou + '/' + pic[:-1] + '.jpg' else: url = 'https://images.dmzj.com/img/chapterpic/' + chapterpic_hou + '/' + chapterpic_qian + '/' + pic[:-1] + '.jpg' else: if int(chapterpic_hou) > int(chapterpic_qian): url = 'https://images.dmzj.com/img/chapterpic/' + chapterpic_qian + '/' + chapterpic_hou + '/' + pic + '.jpg' else: url = 'https://images.dmzj.com/img/chapterpic/' + chapterpic_hou + '/' + chapterpic_qian + '/' + pic + '.jpg' pic_name = '%03d.jpg' % (idx + 1) pic_save_path = os.path.join(chapter_save_dir, pic_name) with closing(requests.get(url, headers = download_header, stream = True)) as response: chunk_size = 1024 content_size = int(response.headers['content-length']) if response.status_code == 200: with open(pic_save_path, "wb") as file: for data in response.iter_content(chunk_size=chunk_size): file.write(data) else: print("鏈接異常,url是%s"%url) time.sleep(10)
漫畫下載完成: