當我學了廖大的Python教程後,感受總得作點什麼,正好本身想隨時查閱,因而就開始有了製做PDF這個想法。css
想要把教程變成PDF有三步:html
BeautifulSoup
)wkhtmltopdf
)Beautiful Soup 是一個能夠從HTML或XML文件中提取數據的Python庫.它可以經過你喜歡的轉換器實現慣用的文檔導航,查找,修改文檔的方式.Beautiful Soup會幫你節省數小時甚至數天的工做時間.python
pip3 install BeautifulSoup4
複製代碼
將一段文檔傳入 BeautifulSoup
的構造方法,就能獲得一個文檔的對象, 能夠傳入一段字符串或一個文件句柄.正則表達式
以下所示:shell
from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"))
soup = BeautifulSoup("<html>data</html>")
複製代碼
Beautiful Soup
將複雜 HTML
文檔轉換成一個複雜的樹形結構,每一個節點都是 Python
對象,全部對象能夠概括爲 4 種: Tag , NavigableString , BeautifulSoup , Comment .
cookie
Tag
:通俗點講就是 HTML
中的一個個標籤,相似 div,p
。NavigableString
:獲取標籤內部的文字,如,soup.p.string
。BeautifulSoup
:表示一個文檔的所有內容。Comment:Comment
對象是一個特殊類型的 NavigableString
對象,其輸出的內容不包括註釋符號.Tag
就是html
中的一個標籤,用BeautifulSoup
就能解析出來Tag
的具體內容,具體的格式爲soup.name
,其中name
是html
下的標籤,具體實例以下:app
print soup.title
輸出title
標籤下的內容,包括此標籤,這個將會輸出<title>The Dormouse's story</title>
複製代碼
print soup.head
輸出head
標籤下的內容<head><title>The Dormouse's story</title></head>
複製代碼
若是 Tag 對象要獲取的標籤有多個的話,它只會返回因此內容中第一個符合要求的標籤。ide
每一個 Tag
有兩個重要的屬性 name
和 attrs
:函數
name
:對於Tag
,它的name
就是其自己,如soup.p.name
就是p
attrs
是一個字典類型的,對應的是屬性-值,如print soup.p.attrs
,輸出的就是{'class': ['title'], 'name': 'dromouse'}
,固然你也能夠獲得具體的值,如print soup.p.attrs['class']
,輸出的就是[title]
是一個列表的類型,由於一個屬性可能對應多個值,固然你也能夠經過get方法獲得屬性的,如:print soup.p.get('class')
。還能夠直接使用print soup.p['class']
get
方法用於獲得標籤下的屬性值,注意這是一個重要的方法,在許多場合都能用到,好比你要獲得<img src="#">
標籤下的圖像url
,那麼就能夠用soup.img.get('src')
,具體解析以下:工具
# 獲得第一個p標籤下的src屬性
print soup.p.get("class")
複製代碼
獲得標籤下的文本內容,只有在此標籤下沒有子標籤,或者只有一個子標籤的狀況下才能返回其中的內容,不然返回的是None
具體實例以下:
# 在上面的一段文本中p標籤沒有子標籤,所以可以正確返回文本的內容
print soup.p.string
# 這裏獲得的就是None,由於這裏的html中有不少的子標籤
print soup.html.string
複製代碼
get_text()
能夠得到一個標籤中的全部文本內容,包括子孫節點的內容,這是最經常使用的方法。
BeautifulSoup 主要用來遍歷子節點及子節點的屬性,經過Tag
取屬性的方式只能得到當前文檔中的第一個 tag,例如,soup.p
。若是想要獲得全部的<p>
標籤,或是經過名字獲得比一個 tag 更多的內容的時候,就須要用到 find_all()
find_all(name, attrs, recursive, text, **kwargs )
複製代碼
find_all是用於搜索節點中全部符合過濾條件的節點。
name參數:是Tag的名字,如p,div,title
# 1. 節點名
print(soup.find_all('p'))
# 2. 正則表達式
print(soup.find_all(re.compile('^p')))
# 3. 列表
print(soup.find_all(['p', 'a']))
複製代碼
另外 attrs 參數能夠也做爲過濾條件來獲取內容,而 limit 參數是限制返回的條數。
以 CSS 語法爲匹配標準找到 Tag。一樣也是使用到一個函數,該函數爲select()
,返回類型是 list。它的具體用法以下:
# 1. 經過 tag 標籤查找
print(soup.select(head))
# 2. 經過 id 查找
print(soup.select('#link1'))
# 3. 經過 class 查找
print(soup.select('.sister'))
# 4. 經過屬性查找
print(soup.select('p[name=dromouse]'))
# 5. 組合查找
print(soup.select("body p"))
複製代碼
- wkhtmltopdf主要用於HTML生成PDF。
- pdfkit是基於wkhtmltopdf的python封裝,支持URL,本地文件,文本內容到PDF的轉換,其最終仍是調用wkhtmltopdf命令。
先安裝wkhtmltopdf,再安裝pdfkit。
pip3 install pdfkit
複製代碼
import pdfkit
pdfkit.from_url('http://google.com', 'out.pdf')
pdfkit.from_file('index.html', 'out.pdf')
pdfkit.from_string('Hello!', 'out.pdf')
複製代碼
pdfkit.from_url(['google.com', 'baidu.com'], 'out.pdf')
pdfkit.from_file(['file1.html', 'file2.html'], 'out.pdf')
複製代碼
with open('file.html') as f:
pdfkit.from_file(f, 'out.pdf')
複製代碼
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'),
],
'no-outline': None,
'outline-depth': 10,
}
pdfkit.from_url('http://google.com', 'out.pdf', options=options)
複製代碼
爬取十幾篇教程以後觸發了這個錯誤:
看來廖大的反爬蟲作的很好,因而只好使用代理ip了,嘗試了免費的西刺免費代理後,最後選擇了付費的 蘑菇代理 ,感受響應速度和穩定性還OK。
運行過程截圖:
生成的效果圖:
代碼以下:
import re
import time
import pdfkit
import requests
from bs4 import BeautifulSoup
# 使用 阿布雲代理
def get_soup(target_url):
proxy_host = "http-dyn.abuyun.com"
proxy_port = "9020"
proxy_user = "**"
proxy_pass = "**"
proxy_meta = "http://%(user)s:%(pass)s@%(host)s:%(port)s" % {
"host": proxy_host,
"port": proxy_port,
"user": proxy_user,
"pass": proxy_pass,
}
proxies = {
"http": proxy_meta,
"https": proxy_meta,
}
headers = {'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
flag = True
while flag:
try:
resp = requests.get(target_url, proxies=proxies, headers=headers)
flag = False
except Exception as e:
print(e)
time.sleep(0.4)
soup = BeautifulSoup(resp.text, 'html.parser')
return soup
def get_toc(url):
soup = get_soup(url)
toc = soup.select("#x-wiki-index a")
print(toc[0]['href'])
return toc
# ⬇️教程html
def download_html(url, depth):
soup = get_soup(url)
# 處理目錄
if int(depth) <= 1:
depth = '1'
elif int(depth) >= 2:
depth = '2'
title = soup.select("#x-content h4")[0]
new_a = soup.new_tag('a', href=url)
new_a.string = title.string
new_title = soup.new_tag('h' + depth)
new_title.append(new_a)
print(new_title)
# 加載圖片
images = soup.find_all('img')
for x in images:
x['src'] = 'https://static.liaoxuefeng.com/' + x['data-src']
# 將bilibili iframe 視頻更換爲連接地址
iframes = soup.find_all('iframe', src=re.compile("bilibili"))
for x in iframes:
x['src'] = "http:" + x['src']
a_tag = soup.new_tag("a", href=x['src'])
a_tag.string = "vide play:" + x['src']
x.replace_with(a_tag)
div_content = soup.find('div', class_='x-wiki-content')
return new_title, div_content
def convert_pdf(template):
html_file = "python-tutorial-pdf.html"
with open(html_file, mode="w") as code:
code.write(str(template))
pdfkit.from_file(html_file, 'python-tutorial-pdf.pdf')
if __name__ == '__main__':
# html 模板
template = BeautifulSoup(
'<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="https://cdn.liaoxuefeng.com/cdn/static/themes/default/css/all.css?v=bc43d83"> <script src="https://cdn.liaoxuefeng.com/cdn/static/themes/default/js/all.js?v=bc43d83"></script> </head> <body> </body> </html>',
'html.parser')
# 教程目錄
toc = get_toc('https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000')
for i, x in enumerate(toc):
url = 'https://www.liaoxuefeng.com' + x['href']
# ⬇️教程html
content = download_html(url, x.parent['depth'])
# 往template添加新的教程
new_div = template.new_tag('div', id=i)
template.body.insert(3 + i, new_div)
new_div.insert(3, content[0])
new_div.insert(3, content[1])
time.sleep(0.4)
convert_pdf(template)
複製代碼
參考文檔: