學以致用:Python爬取廖大Python教程制作pdf


python-tutorial-pdf

當我學了廖大的Python教程后,感覺總得做點什么,正好自己想隨時查閱,於是就開始有了制作PDF這個想法。

想要把教程變成PDF有三步:

  1. 先生成空html,爬取每一篇教程放進一個新生成的div,這樣就生成了包含所有教程的html文件(BeautifulSoup)
  2. 將html轉換成pdf(wkhtmltopdf)
  3. 由於廖大是寫教程的,反爬做的比較好,在爬取的過程中還需要代理ip(阿布雲代理)

BeautifulSoup

Beautiful Soup 是一個可以從HTML或XML文件中提取數據的Python庫.它能夠通過你喜歡的轉換器實現慣用的文檔導航,查找,修改文檔的方式.Beautiful Soup會幫你節省數小時甚至數天的工作時間.

安裝

pip3 install BeautifulSoup4

開始使用

將一段文檔傳入 BeautifulSoup 的構造方法,就能得到一個文檔的對象, 可以傳入一段字符串或一個文件句柄.

如下所示:

from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"))
soup = BeautifulSoup("<html>data</html>")
  • 首先,文檔被轉換成Unicode,並且HTML的實例都被轉換成Unicode編碼.
  • 然后,Beautiful Soup選擇最合適的解析器來解析這段文檔,如果手動指定解析器那么Beautiful Soup會選擇指定的解析器來解析文檔.

對象的種類

Beautiful Soup 將復雜 HTML 文檔轉換成一個復雜的樹形結構,每個節點都是 Python 對象,所有對象可以歸納為 4 種: Tag , NavigableString , BeautifulSoup , Comment .

  • Tag:通俗點講就是 HTML 中的一個個標簽,類似 div,p
  • NavigableString:獲取標簽內部的文字,如,soup.p.string
  • BeautifulSoup:表示一個文檔的全部內容。
  • Comment:Comment 對象是一個特殊類型的 NavigableString 對象,其輸出的內容不包括注釋符號.

Tag

Tag就是html中的一個標簽,用BeautifulSoup就能解析出來Tag的具體內容,具體的格式為soup.name,其中namehtml下的標簽,具體實例如下:

  • print soup.title輸出title標簽下的內容,包括此標簽,這個將會輸出
    <title>The Dormouse's story</title>
    
  • print soup.head輸出head標簽下的內容
    <head><title>The Dormouse's story</title></head>
    

如果 Tag 對象要獲取的標簽有多個的話,它只會返回所以內容中第一個符合要求的標簽

Tag 屬性

每個 Tag 有兩個重要的屬性 nameattrs

  • 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

get方法用於得到標簽下的屬性值,注意這是一個重要的方法,在許多場合都能用到,比如你要得到<img src="#">標簽下的圖像url,那么就可以用soup.img.get('src'),具體解析如下:

# 得到第一個p標簽下的src屬性
print soup.p.get("class")   

string

得到標簽下的文本內容,只有在此標簽下沒有子標簽,或者只有一個子標簽的情況下才能返回其中的內容,否則返回的是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 選擇器

以 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

  • wkhtmltopdf主要用於HTML生成PDF。
  • pdfkit是基於wkhtmltopdf的python封裝,支持URL,本地文件,文本內容到PDF的轉換,其最終還是調用wkhtmltopdf命令。

安裝

先安裝wkhtmltopdf,再安裝pdfkit。

  1. https://wkhtmltopdf.org/downloads.html
  2. pdfkit
    pip3 install pdfkit
    

轉換url/file/string

import pdfkit

pdfkit.from_url('http://google.com', 'out.pdf')
pdfkit.from_file('index.html', 'out.pdf')
pdfkit.from_string('Hello!', 'out.pdf')

轉換url或者文件名列表

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

爬取十幾篇教程之后觸發了這個錯誤:
503

看來廖大的反爬蟲做的很好,於是只好使用代理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)

參考文檔

  1. Beautiful Soup 文檔
  2. HTML 轉 PDF 之 wkhtmltopdf 工具精講


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM