本人是个”收集狂”(不要想歪了哈,我只是喜欢收藏技术贴),遇到好的东西就喜欢收藏或记录下来,尤其是好的技术文章或者工具。这里就要提到廖雪峰老师的官方网站了,廖老师写的Python、JavaScript、Git教程真心好呀,每每都要去逛逛。于是就有了今天这个需求,把廖老师的教程由网页转为PDF电子书,这样就可以随时随地离线学习加收藏了。说到这里进入今天的主题Python爬虫实践:将网页转换为pdf电子书
写爬虫似乎没有比用 Python 更合适了,Python 社区提供的爬虫工具多得让你眼花缭乱,各种拿来就可以直接用的 library 分分钟就可以写出一个爬虫出来,今天就琢磨着写一个爬虫,将廖雪峰的 Python 教程 爬下来做成 PDF 电子书方便离线阅读。
开始写爬虫前,我们先来分析一下该网站的页面结构,网页的左侧是教程的目录大纲,每个 URL 对应到右边的一篇文章,右侧上方是文章的标题,中间是文章的正文部分,正文内容是我们关心的重点,我们要爬的数据就是所有网页的正文部分,下方是用户的评论区,评论区对我们没什么用,所以可以忽略它。
工具准备
弄清楚了网站的基本结构后就可以开始准备爬虫所依赖的工具包了。requests、beautifulsoup 是爬虫两大神器,reuqests 用于网络请求,beautifusoup 用于操作 html 数据。有了这两把梭子,干起活来利索,scrapy 这样的爬虫框架我们就不用了,小程序派上它有点杀鸡用牛刀的意思。此外,既然是把 html 文件转为 pdf,那么也要有相应的库支持, wkhtmltopdf 就是一个非常好的工具,它可以用适用于多平台的 html 到 pdf 的转换,pdfkit 是 wkhtmltopdf 的Python封装包。首先安装好下面的依赖包,接着安装 wkhtmltopdf
1
2
3
4
pip install requests
pip install beautifulsoup4
pip install pdfkit
pip install PyPDF2
安装 wkhtmltopdf
Ubuntu 和 CentOS 可以直接用命令行进行安装。
1
2
$ sudo apt-get install wkhtmltopdf # ubuntu
$ sudo yum intsall wkhtmltopdf # centos
Windows平台直接在 wkhtmltopdf 官网2下载稳定版的进行安装,安装完成之后把该程序的执行路径加入到系统环境 $PATH 变量中,否则 pdfkit 找不到 wkhtmltopdf 就出现错误 No wkhtmltopdf executable found
这里就要多说几句了,因为这里处理不好,程序执行pdfkit.from_file(htmls, file_name, options=options)
的时候就会报错。
现在开始手动安装wkhtmltopdf(博主电脑操作系统是 macOS 10.12.2)
1、去官网下wkhtmltopdf 。下载完成运行wkhtmltox-0.12.4_osx-cocoa-x86-64.pkg
2、把wkhtmltoimage和wkhtmltopdf复制到/usr/bin目录,更改所有者,并增加可执行属性
1
2
3
4
5
6
sudo cp /usr/local/bin/wkhtmltopdf /usr/bin/
sudo cp /usr/local/bin/wkhtmltoimage /usr/bin/
sudo chown root:root /usr/bin/wkhtmltopdf
sudo chown root:root /usr/bin/wkhtmltoimage
sudo chmod +x /usr/bin/wkhtmltopdf
sudo chmod +x /usr/bin/wkhtmltoimage
不出意外你在执行第一句的时候就会遇到chmod:Unable to change file modle on /usr/bin
这是因为苹果从 OS X El Capitan 10.11 系统开始使用了 Rootless 机制,可以将该机制理解为一个更高等级的系统的内核保护措施,系统默认将会锁定 /system、/sbin、/usr 这三个目录。
关闭Rootless
关闭和开启 Rootless 非常简单,方法如下:重启 Mac,听到开机启动声后按下 Command+R,进入恢复模式,在上面的菜单实用工具中找到并打开 Terminal(如果顶部没出现菜单,请继续重启^_^)。输入如下命令:
1
2
$ csrutil disable #关闭 Rootless
$ csrutil enable #开启 Rootless
OK,到此我们工具及环境配置好了,下面开始实现功能。
爬虫实现
一切准备就绪后就可以上代码了,不过写代码之前还是先整理一下思绪。程序的目的是要把所有 URL 对应的 html 正文部分保存到本地,然后利用 pdfkit 把这些文件转换成一个 pdf 文件。我们把任务拆分一下,首先是把某一个 URL 对应的 html 正文保存到本地,然后找到所有的 URL 执行相同的操作。用 Chrome 浏览器找到页面正文部分的标签,按 F12 找到正文对应的 div 标签:<div class="x-wiki-content">
,该 div 是网页的正文内容。用 requests 把整个页面加载到本地后,就可以使用 beautifulsoup 操作 HTML 的 dom 元素 来提取正文内容了。
具体的实现代码如下:用 soup.find_all 函数找到正文标签,然后把正文部分的内容保存到 a.html 文件中。
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
def parse_url_to_html(url, name):
"""
解析URL,返回HTML内容
:param url:解析的url
:param name: 保存的html文件名
:return: html
"""
try:
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# 正文
body = soup.find_all(class_="x-wiki-content")[0]
# 标题
title = soup.find('h4').get_text()
# 标题加入到正文的最前面,居中显示
center_tag = soup.new_tag("center")
title_tag = soup.new_tag('h1')
title_tag.string = title
center_tag.insert(1, title_tag)
body.insert(1, center_tag)
html = str(body)
# body中的img标签的src相对路径的改成绝对路径
pattern = "(<img .*?src=\")(.*?)(\")"
def func(m):
if not m.group(3).startswith("http"):
rtn = m.group(1) + baseUrl + m.group(2) + m.group(3)
return rtn
else:
return m.group(1)+m.group(2)+m.group(3)
html = re.compile(pattern).sub(func, html)
html = html_template.format(content=html)
html = html.encode("utf-8")
with open(name, 'wb') as f:
f.write(html)
return name
except Exception as e:
logging.error("解析错误", exc_info=True)
第二步就是把页面左侧所有 URL 解析出来。采用同样的方式,找到 左侧菜单标签
具体代码实现逻辑:因为页面上有两个uk-nav uk-nav-side的 class 属性,而真正的目录列表是第二个。所有的 url 获取了,url 转 html 的函数在第一步也写好了。
1
2
3
4
5
6
7
8
9
10
11
12
13
def get_url_list():
"""
获取所有URL目录列表
:return:
"""
response = requests.get(htmlUrl)
soup = BeautifulSoup(response.content, "html.parser")
menu_tag = soup.find_all(class_="uk-nav uk-nav-side")[1]
urls = []
for li in menu_tag.find_all("li"):
url = baseUrl + li.a.get('href')
urls.append(url)
return urls
最后一步就是把 html 转换成pdf文件了。转换成 pdf 文件非常简单,因为 pdfkit 把所有的逻辑都封装好了,你只需要调用函数 pdfkit.from_file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def save_pdf(htmls, file_name):
"""
把所有html文件保存到pdf文件
:param htmls: html文件列表
:param file_name: pdf文件名
:return:
"""
options = {
'page-size': 'Letter',
'margin-top': '0.75in',
'margin-right': '0.75in',
'margin-bottom': '0.75in',
'margin-left': '0.75in',
'encoding': "UTF-8",
'custom-header': [
('Accept-Encoding', 'gzip')
],
'cookie': [
('cookie-name1', 'cookie-value1'),
('cookie-name2', 'cookie-value2'),
],
'outline-depth': 10,
}
pdfkit.from_file(htmls, file_name, options=options)
执行 save_pdf 函数,电子书 pdf 文件就生成了:
程序执行图
生成PDF文件