用requests和selenium下载漫画

用requests和selenium下载漫画

有好久都没写爬虫的代码了, 当年自学python很大程度上就是为了白嫖网站上的漫画和小说资源, 下载到手机上看. 因为有的时候网络很差的时候图片很可能半天加载不出来.

我在微博上看到有人推荐”有花无实”的漫画很好看, 但是发现大多是正规网站上这部漫画已经下架了, 大妈之家上面都没有. 在一个叫”爱漫画”的网站上有, 于是我准备下载一波. 很长时间没写爬漫画的代码了, 断断续续写了一个半小时终于可以运行了. 在下载的这段时间记录一下如何实现下载这个代码的.

requests 获取网页

requests 库可以很方便的获取网页的 html 文件:

1
2
3
4
import requests
index_url = "https://www.iimanhua.cc/comic/38692/index.html"
requ = requests.get(index_url)
print(requ.text)

其中 requ.text 会返回 html 文件的字符串信息. 类似的可以用 requ.content 返回二进制文件信息(例如图片). 如果想要下载图片就可以直接使用

1
2
3
4
img_url = "https://res.img.96youhuiquan.com/images/2021/06/24/15/41df606a2f.jpg"
requ = request.get(img_url)
with open("cover.jpg", "wb") as file:
file.write(requ.content)

但是有的时候会有一些问题, 就是网站会自动识别到你并不是以正常手段进入的网站, 毕竟你是通过非正常手段, 也就是requests库访问的, 因此很可能会直接拒绝你的访问. 不过在这个时候使用一些简单的伪装即可通过这种简单的识别

1
2
3
4
5
6
7
import requests
index_url = "https://www.iimanhua.cc/comic/38692/index.html"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"
}
requ = requests.get(index_url, headers=headers)
print(requ.text)

因为获取网页很可能会报错, 我们希望这样的情况不会影响到我们运行的成功性. 我们可以对于获取失败的情况进行多次获取, 减少失败的概率. 假设获取一次失败的概率为 $\frac12$, 那么重复 10 次成功的概率就是 $1-(1-\frac12)^{10}<10^{-3}$.

在这里我随便选取的错误 SyntaxError 确切的说不是语法错误, 我也不知道该写什么错误, 不过不要紧, 反正自己清楚就好了. 在调用一次request.get 的函数末尾, 我使用了一个 sleep 函数, 这是因为大多服务器都会限制访问的频率, 如果频率太高会给服务器带来严重的负担. 因此我设置在两次访问的间隔不超过 0.5 秒.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from time import sleep

def request(url):
for _ in range(10):
try:
return requestUrl(url)
except SyntaxError:
pass
raise SyntaxError()


def requestUrl(url):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"
}
requ = requests.get(url, headers=headers)
if requ.status_code != 200:
raise SyntaxError()
sleep(.5)
return requ

BeautifulSoup 解析网页

获取网页之后我们必须解析网页, 否则只靠正则表达式去识别网页中我们需要的内容还是太勉为其难了, 也很不优雅. 所以我们使用 BeautifulSoup 库来解析 html 文件.

BeautifulSoup 库的名称来源是爱丽丝漫游仙境, 意思就是将杂乱无章的 html 代码整理成漂亮的形式(充满食欲的汤?应该这么翻译吗?). 可能这就是理工人的浪漫了吧hhh.

1
soup = BeautifulSoup(requ.text, "html.parser")

解析完网页之后显然我们还需要在网页中寻找到有用的信息, 我们可以调用 Beautiful 库中的函数来检索我们需要的元素. 因为我们可以看到漫画的主页上漫画列表, 直接右键选择 检查, 就可以找到对应的每一章的链接所在的位置:

1
2
3
4
5
6
7
8
<div class="plist pmedium max-h200" id="play_0">
<ul>
<li><a href="/comic/38692/745036.html" title="第28话">第28话</a></li>
<li><a href="/comic/38692/727562.html" title="第27话">第27话</a></li>
<li><a href="/comic/38692/715434.html" title="第26话">第26话</a></li>
...
</ul>
</div>

因此直接查找 id=play_0 元素, 再从中找出 tag_name=a 的所有元素的 href 属性, 就可以得到每一章对应的链接.
1
2
3
4
5
6
7
8
soup = BeautifulSoup(requ.text, "html.parser")
lists = soup.find(id="play_0").find_all("a")
epis = []
for link in lists:
if link['href'].startswith("http"):
epis.append(link['href'])
else:
epis.append("https://www.iimanhua.cc/" + link['href'])

在这里 epis 存储了每一章的 url.

selenium 打开网页

接着我们需要访问关于每一章的网页, 并且下载当中的图片. 按道理其实是可以类似于前两步, 使用 requests 库和 BeautifulSoup 库获取每一页图片的链接, 并使用 requests 库获得图片的二进制信息存储到计算机中.

打开第一章的网页, 使用 F12 找到对应的图片的链接以及位置:

1
2
3
4
5
6
7
8
9
10
<div class="viewimages" id="viewimages">
<div id="loading" style="padding: 10px; display: none;">
<img src="/skin/2014mh/pic_loading.gif" align="absmiddle"> 图片载入中,请稍后...如载入时间过慢请尝试切换右上角的服务器
</div>
<img src="https://res.img.96youhuiquan.com/images/2021/06/24/15/41df606a2f.jpg" onerror="setimgerror()" onload="loadnextimg(this)">
<img src="https://res.img.96youhuiquan.com/images/2021/06/24/15/410ee8464d.jpg" onerror="setimgerror()" onload="loadnextimg(this)">
<img src="https://res.img.96youhuiquan.com/images/2021/06/24/15/41777669fc.jpg" onerror="setimgerror()" onload="loadnextimg(this)">
...
<img src="https://res.img.96youhuiquan.com/images/2021/06/24/15/4143433e42.jpg" onerror="setimgerror()" onload="loadnextimg(this)" onclick="gonext()" alt="单击进入下一章" id="viewimg" style="cursor:hand;cursor:pointer;">
</div>

也就是通过 id=viewimages 找到图片列表的位置, 然后找其中的所有子 img 节点, 取得其 src 属性.

但是可能是为了防止爬虫, 或者为了效率? 这个网站使用了 Ajax 异步渲染出图片以及图片的链接. 这样对于我们直接获取 html 之后是得不到图片的链接的. 这是我们获取到的 html 文件.

1
2
3
4
5
<div class="viewimages" id="viewimages">
<div ondblclick=nextPage() id="show_p">
<img src="/skin/2014mh/pic_loading.gif" alt="loading..." />
</div>
</div>

里面什么都没有. 这是因为源文件当中什么也没有, 但是图片链接是 .js 文件中的函数异步渲染出来的, 因此如果没有 js 文件渲染, 图片链接是不可能得到的.

这时候使用 selenium 库通过调用 chrome 控制浏览器打开网页是比较简便的.

1
2
web = Chrome(executable_path="D:\\VsCode\\PYProject\\chromedriver.exe")
web.get(url)

第一行中的 executable_path 并不是必须填写的变量, 如果你的 chromedriverPATH 当中, 那么空着也可以. 而 web.get 函数可以控制 chrome 打开网页.

获取标题

web.title 可以直接获得标题, 得到的结果如下

1
有花无实漫画 第1话  - 有花无实漫画下拉式免费观看 - 爱漫画

我们希望去掉后面的后缀, 我们可以使用正则表达式库 re 来匹配这样的结果(以 - 为分割)

1
2
3
import re

title = re.match("([^-]*?)\s+[-]", web.title).group(1)

其中 re.match 可以匹配字符串 web.title 中从头开始的一串字符. 用 () 分割我们需要的内容, 最后如果是 .group(0) 获得的就是匹配的全部字符, 如果是 .group(n) 就表示匹配的第 n 个括号中对应的那些字符. 而中括号 [] 括起来的内容表示所有可以候选(可匹配)的一个字符, 比如 [0-9] 可以匹配 所有数字, 而匹配字母 abc 当中的一个可以用 [abc] 表示. 同样, [^-] 表示可以匹配除 - 之外任意字符. 而在某个字符或者中括号后面跟一个 *? 表示可以重复匹配 0 到 $\infty$ 次该字符, + 表示可以重复匹配 1 到 $\infty$ 次该字符. 而有一些特殊符号可以简写, 比如 \s 匹配所有空格符/空行符/制表符, 而 \S 匹配除空格符/换行符/制表符之外的任意字符. 在代码中 "([^-]*?)\s+[-]" 表示匹配除 - 符之外的字符一直到出现 “空格-“.

获取图片

在使用 selenium 打开了网页, 需要使用 selenium 搜索对应的 img 标签. 使用 .find_elements 函数即可, 图片的链接在 img 标签中的 src 属性, 获得 img 标签后使用 .get_attribute 函数获得 src 属性.

1
2
3
4
5
6
from selenium.webdriver.common.by import By

imgs = web.find_elements(By.CSS_SELECTOR, "#viewimages>img")
pics = []
for img in imgs:
pics.append(img.get_attribute('src')

之后再使用获得的图片链接, 通过 requests 库下载图片到本地即可.

最后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import requests
from bs4 import BeautifulSoup
from time import sleep
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
import os, re

def request(url):
for _ in range(10):
try:
return requestUrl(url)
except SyntaxError:
pass
raise SyntaxError()


def requestUrl(url):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"
}
requ = requests.get(url, headers=headers)
if requ.status_code != 200:
raise SyntaxError(f"ERROR: {requ.status_code}")
sleep(.5)
return requ


def main():
index_url = "https://www.iimanhua.cc/comic/38692/index.html"

requ = request(index_url)
soup = BeautifulSoup(requ.text, "html.parser")
lists = soup.find(id="play_0").find_all("a")

epis = []
for link in lists:
if link['href'].startswith("http"):
epis.append(link['href'])
else:
epis.append("https://www.iimanhua.cc/" + link['href'])

print("line 30")
web = Chrome(executable_path="D:\\VsCode\\PYProject\\chromedriver.exe")
for epi in epis:
web.get(epi)
web.implicitly_wait(20)
title = re.match("([^-]*?)\s+[-]", web.title).group(1)
print(f"\ndownloading {title} ...")

if not os.path.isdir(title):
os.mkdir(title)

imgs = web.find_elements(By.CSS_SELECTOR, "#viewimages>img")
pics = []
for img in imgs:
pics.append(img.get_attribute("src"))
for no, pic in enumerate(pics):
img = request(pic).content
file_name = title+f"/{no+1:03d}.jpg"
if os.path.exists(file_name):
print("\rexist " + file_name, end="")
break
print("\rwrite " + file_name, end="")
with open(file_name, "wb") as file:
file.write(img)
web.close()

if __name__ == '__main__':
main()

文章作者: Letter Wu
文章链接: https://letterwu.github.io/2022/03/19/用requests和selenium下载漫画/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Have a nice day~ | Letter Wu's BLOG
支付宝打赏
微信打赏