整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Python轉(zhuǎn)換HTML為PDF

          文的文字及圖片來(lái)源于網(wǎng)絡(luò),僅供學(xué)習(xí)、交流使用,不具有任何商業(yè)用途,,版權(quán)歸原作者所有,如有問(wèn)題請(qǐng)及時(shí)聯(lián)系我們以作處理

          作者:州的先生

          原文鏈接: https://zmister.com/archives/1607.html

          將 HTML 網(wǎng)頁(yè)轉(zhuǎn)換為 PDF 是很多人常見(jiàn)的一個(gè)需求,在瀏覽器上,我們可以通過(guò)瀏覽器的“打印”功能直接將網(wǎng)頁(yè)打印輸出為 PDF。

          但是如果有多個(gè)網(wǎng)頁(yè)就不好辦了。

          文章目錄

          二進(jìn)制軟件

          網(wǎng)絡(luò)上存在很多將 HTML 轉(zhuǎn)換為 PDF 的軟件和工具。比較著名的有 Carelib、wkhtmltopdf。

          whtmltopdf

          wkhtmltopdf 真是一個(gè)優(yōu)秀的 HTML 轉(zhuǎn)換 PDF 工具。其借助 Qt 的 WebKit 渲染引擎,將 HTML 文檔渲染導(dǎo)出為 PDF 文檔或圖像。

          功能十分完善,但是由于使用的渲染引擎是 Qt 的 WebKit,其沒(méi)法對(duì) ES6 的 JavaScript 代碼提供支持,導(dǎo)致一些采用 ES6 編寫的 HTML 頁(yè)面渲染不出實(shí)際的效果來(lái),導(dǎo)致州的先生最終放棄了它。

          Carelib

          Carelib 是一個(gè)電子書管理軟件,其中提供了各類文檔的轉(zhuǎn)換工具,所以可以借助其電子書轉(zhuǎn)換工具來(lái)實(shí)現(xiàn) HTMl 到 PDF 的轉(zhuǎn)換。

          這些都是用于桌面環(huán)境的二進(jìn)制軟件,如果要在 Python 中使用,要么使用 Popen() 方法調(diào)用這些二進(jìn)制軟件的命令,要么使用一些第三方的封裝模塊,比如: pdfkit 、 pypandoc 等,這些第三方模塊通過(guò)集成調(diào)用上述二進(jìn)制軟件,封裝了一些方便 Python 調(diào)用的接口。

          純 Python 庫(kù)實(shí)現(xiàn)

          上面介紹的那些 Python 第三方模塊雖然可以很好的進(jìn)行 HTML 到 PDF 的轉(zhuǎn)換工作,但是都需要額外在計(jì)算機(jī)上安裝其他的二進(jìn)制軟件,很多小伙伴并不喜歡這種調(diào)用方式。

          不依賴于二進(jìn)制軟件的實(shí)現(xiàn),有如下的方案:

          xhtml2pdf

          這是一個(gè)基于 ReportLab、html5lib、PyPDF2 等 Python 模塊構(gòu)建的 HTML 到 PDF 轉(zhuǎn)換模塊。能夠很好的支持 HTML5 、CSS2.1 和部分 CSS3 語(yǔ)法。

          因?yàn)槭腔?Report Lab 模塊進(jìn)行的開(kāi)發(fā),其對(duì)中文的支持在某些環(huán)境下會(huì)有問(wèn)題。而且由于開(kāi)發(fā)人員的變更,模塊的功能出現(xiàn)了一些斷層。但是仍然是一個(gè)非常棒的 HTML 轉(zhuǎn) PDF 模塊。

          weasyprint

          這是一個(gè)用于 HTML 和 CSS 的可視化渲染引擎,可以將 HTML 文檔導(dǎo)出為打印標(biāo)準(zhǔn)的 PDF 文件。

          xhtml2pdf 模塊也曾推薦使用這個(gè)模塊來(lái)進(jìn)行 HTML 轉(zhuǎn)換 PDF 的工作。

          這個(gè)模塊功能很強(qiáng)大、效果很出色,但是,模塊的依賴項(xiàng)太多了:

          州的先生至今沒(méi)有在 Windows 電腦上安裝成功過(guò)!

          瀏覽器方案

          在上述兩種方案中,二進(jìn)制程序的可控制性稍有不足,而純 Python 實(shí)現(xiàn)的渲染解析則在功能上和依賴上不是有友好。

          處理上述兩種方案,我們還能采用第三種方式進(jìn)行 HTMl 到 PDF 的轉(zhuǎn)換。那就是借助 Web 自動(dòng)化測(cè)試的瀏覽器內(nèi)核和 Qt for Python 的 Web 引擎 來(lái)實(shí)現(xiàn)。

          Web 自動(dòng)化的瀏覽器內(nèi)核

          使用 Python 的小伙伴經(jīng)常會(huì)使用 Selenium、pyppeteer 這兩個(gè) Web 自動(dòng)化測(cè)試的模塊來(lái)進(jìn)行數(shù)據(jù)采集和 Web 自動(dòng)化測(cè)試工作。

          這兩個(gè)模塊都是用來(lái)驅(qū)動(dòng)一個(gè)真實(shí)的瀏覽器來(lái)進(jìn)行網(wǎng)頁(yè)的操作。正是基于此,我們可以調(diào)用瀏覽器中打印相關(guān)的 API 接口,來(lái)實(shí)現(xiàn) HTML 轉(zhuǎn) PDF 的功能。

          例如,在 pyppeteer 中可以按照下面示例的方式,打開(kāi)一個(gè) HTML 文檔,然后將其轉(zhuǎn)換為 PDF 文檔:

          Qt 的 Web 引擎

          在 Qt5 中,Qt 使用新的 Chromium 內(nèi)核代替了老舊的 WebKit 作為 Web 的渲染引擎。使得在 Qt 中進(jìn)行可以現(xiàn)代化的瀏覽器開(kāi)發(fā)。

          借助于 Qt 的 Python 實(shí)現(xiàn)(PyQt5 系列 和 PySide2 系列),我們可以直接調(diào)用 Qt 中的 Web 引擎相關(guān)的接口。

          其中 QtWebEngineWidgets 子模塊中的 QWebEngineView() 類提供了 printToPdf 方法供我們將網(wǎng)頁(yè)打印為 PDF 文檔,所以基于此,我們也可以使用 PyQt5 或 PySide2 進(jìn)行 HTML 轉(zhuǎn)換 PDF,示例如下所示:

          markdown中寫下你的文章,并使用Python將它們轉(zhuǎn)換成HTML-作者Florian Dahlitz,于2020年5月18日(15分鐘)

          介紹

          幾個(gè)月前,我想開(kāi)通自己的博客,而不是使用像Medium這樣的網(wǎng)站。這是一個(gè)非常基礎(chǔ)的博客,所有的文章都是HTML形式的。然而,有一天,我突然產(chǎn)生了自己編寫Markdown到HTML生成器的想法,最終這將允許我用markdown來(lái)編寫文章。此外,為它添加諸如估計(jì)閱讀時(shí)間之類的擴(kuò)展特性會(huì)更容易。長(zhǎng)話短說(shuō),我實(shí)現(xiàn)了自己的markdown到HTML生成器,我真的很喜歡它!

          在本系列文章中,我想向您展示如何構(gòu)建自己的markdown到HTML生成器。該系列由三部分組成:

          • 第一部分(本文)介紹了整個(gè)管線的實(shí)現(xiàn)。

          • 第二部分通過(guò)一個(gè)模塊擴(kuò)展了實(shí)現(xiàn)的管線,該模塊用于計(jì)算給定文章的預(yù)計(jì)閱讀時(shí)間。

          • 第三部分演示如何使用管線生成自己的RSS摘要。

          這三部分中使用的代碼都可以在GitHub上找到。

          備注:我的文章中markdown到HTML生成器的想法基于Anthony Shaw文章中的實(shí)現(xiàn)。

          項(xiàng)目構(gòu)建

          為了遵循本文的內(nèi)容,您需要安裝幾個(gè)軟件包。我們把它們放進(jìn)requirements.txt文件。

          Markdown是一個(gè)包,它允許您將markdown代碼轉(zhuǎn)換為HTML。之后我們用Flask產(chǎn)生靜態(tài)文件。

          但在安裝之前,請(qǐng)創(chuàng)建一個(gè)虛擬環(huán)境,以避免Python安裝出現(xiàn)問(wèn)題:

          激活后,您可以使用pip安裝requirements.txt中的依賴。

          很好!讓我們創(chuàng)建幾個(gè)目錄來(lái)更好地組織代碼。首先,我們創(chuàng)建一個(gè)app目錄。此目錄包含我們提供博客服務(wù)的Flask應(yīng)用程序。所有后續(xù)目錄都將在app目錄內(nèi)創(chuàng)建。其次,我們創(chuàng)建一個(gè)名為posts的目錄。此目錄包含要轉(zhuǎn)換為HTML文件的markdown文件。接下來(lái),我們創(chuàng)建一個(gè)templates目錄,其中包含稍后使用Flask展示的模板。在templates目錄中,我們?cè)賱?chuàng)建兩個(gè)目錄:

          posts包含生成的HTML文件,這些文件與應(yīng)用程序根目錄中posts目錄中的文件相對(duì)應(yīng)。

          shared包含在多個(gè)文件中使用的HTML文件。

          此外,我們還創(chuàng)建了一個(gè)名為services的目錄。該目錄將包含我們?cè)贔lask應(yīng)用程序中使用的模塊,或者為它生成某些東西。最后,創(chuàng)建一個(gè)名為static的目錄帶有兩個(gè)子目錄images和css。自定義CSS文件和文章的縮略圖將存儲(chǔ)在此處。

          您的最終項(xiàng)目結(jié)構(gòu)應(yīng)如下所示:

          令人驚嘆!我們完成了一般的項(xiàng)目設(shè)置。我們來(lái)看看Flask的設(shè)置。

          Flask設(shè)置

          路由

          我們?cè)谏弦还?jié)安裝了Flask。但是,我們?nèi)匀恍枰粋€(gè)Python文件來(lái)定義用戶可以訪問(wèn)的端點(diǎn)。在app目錄中創(chuàng)建main.py并將以下內(nèi)容復(fù)制到其中。

          該文件定義了一個(gè)具有兩個(gè)端點(diǎn)的基礎(chǔ)版Flask應(yīng)用程序。用戶可以使用/route訪問(wèn)第一個(gè)端點(diǎn)返回索引頁(yè),其中列出了所有文章。

          第二個(gè)端點(diǎn)是更通用的端點(diǎn)。它接受post的名稱并返回相應(yīng)的HTML文件。

          接下來(lái),我們通過(guò)向app目錄中添加一個(gè)__init__.py,將其轉(zhuǎn)換為一個(gè)Python包。此文件為空。如果您使用UNIX計(jì)算機(jī),則可以從項(xiàng)目的根目錄運(yùn)行以下命令:

          模板

          現(xiàn)在,我們創(chuàng)建兩個(gè)模板文件index.html以及l(fā)ayout.html,都存儲(chǔ)在templates/shared目錄中。這個(gè)layout.html模板將用于單個(gè)博客條目,而index.html模板用于生成索引頁(yè),從中我們可以訪問(wèn)每個(gè)帖子。讓我們從index.html模板開(kāi)始。

          它是一個(gè)基本的HTML文件,其中有兩個(gè)元標(biāo)記、一個(gè)標(biāo)題和兩個(gè)樣式表。注意,我們使用一個(gè)遠(yuǎn)程樣式表和一個(gè)本地樣式表。遠(yuǎn)程樣式表用于啟用Bootstrap[1]類。第二個(gè)是自定義樣式。我們晚點(diǎn)再定義它們。

          HTML文件的主體包含一個(gè)容器,其中包含Jinja2[2]邏輯,用于為每個(gè)post生成Bootstrap卡片[3]。您是否注意到我們不直接基于變量名訪問(wèn)這些值,而是需要將[0]添加到其中?這是因?yàn)槲恼轮薪馕龅脑獢?shù)據(jù)是列表。實(shí)際上,每個(gè)元數(shù)據(jù)元素都是由單一元素組成的列表。我們稍后再看。到目前為止,還不錯(cuò)。讓我們看看layout.html模板。

          如你所見(jiàn),它比前一個(gè)短一點(diǎn),簡(jiǎn)單一點(diǎn)。文件頭與index.html文件很相似,除了我們有不同的標(biāo)題。當(dāng)然,我們可以共用一個(gè)模板,但是我不想讓事情變得更復(fù)雜。

          body中的容器僅定義一個(gè)h1標(biāo)記。然后,我們提供給模板的內(nèi)容被插入并呈現(xiàn)。

          樣式

          正如上一節(jié)所承諾的,我們將查看自定義CSS文件style.css. 我們?cè)趕tatic/css中找到該文件,并根據(jù)需要自定義頁(yè)面。下面是我們將用于基礎(chǔ)示例的內(nèi)容:

          我不喜歡Bootstrap中blockquotes的默認(rèn)外觀,所以我們?cè)谧髠?cè)添加了一點(diǎn)間距和邊框。此外,blockquote段落底部的頁(yè)邊空白將被刪除。不刪除的話看起來(lái)很不自然。

          最后但并非最不重要的是,左右兩邊的填充被刪除。由于兩邊都有額外的填充,縮略圖沒(méi)有正確對(duì)齊,所以在這里刪除它們。

          到現(xiàn)在為止,一直都還不錯(cuò)。我們完成了關(guān)于Flask的所有工作。讓我們開(kāi)始寫一些帖子吧!

          寫文章

          正如標(biāo)題所承諾的,你可以用markdown寫文章-是的!在寫文章的時(shí)候,除了保證正確的markdown格式外,沒(méi)有其他需要注意的事情。

          在完成本文之后,我們需要在文章中添加一些元數(shù)據(jù)。此元數(shù)據(jù)添加在文章之前,并由三個(gè)破折號(hào)分隔開(kāi)來(lái)---。下面是一個(gè)示例文章(post1.md)的摘錄:

          注意:您可以在GitHub庫(kù)的app/posts/post1.md中找到完整的示例文章。

          在我們的例子中,元數(shù)據(jù)由標(biāo)題、副標(biāo)題、類別、發(fā)布日期和index.html中卡片對(duì)應(yīng)縮略圖的路徑組成.

          我們?cè)贖TML文件中使用了元數(shù)據(jù),你還記得嗎?元數(shù)據(jù)規(guī)范必須是有效的YAML。示例形式是鍵后面跟著一個(gè)冒號(hào)和值。最后,冒號(hào)后面的值是列表中的第一個(gè)也是唯一的元素。這就是我們通過(guò)模板中的索引運(yùn)算符訪問(wèn)這些值的原因。

          假設(shè)我們寫完了文章。在我們可以開(kāi)始轉(zhuǎn)換之前,還有一件事要做:我們需要為我們的帖子生成縮略圖!為了讓事情更簡(jiǎn)單,只需從你的電腦或網(wǎng)絡(luò)上隨機(jī)選取一張圖片,命名它為placeholder.jpg并把它放到static/images目錄中。GitHub存儲(chǔ)庫(kù)中兩篇文章的元數(shù)據(jù)包含一個(gè)代表圖像的鍵值對(duì),值是placeholder.jpg。

          注意:在GitHub存儲(chǔ)庫(kù)中,您可以找到我提到的兩篇示例文章。

          markdown到HTML轉(zhuǎn)換器

          最后,我們可以開(kāi)始實(shí)現(xiàn)markdown to HTML轉(zhuǎn)換器。因此,我們使用我們?cè)陂_(kāi)始時(shí)安裝的第三方包Markdown。我們先創(chuàng)建一個(gè)新模塊,轉(zhuǎn)換服務(wù)將在其中運(yùn)行。因此,我們?cè)趕ervice目錄中創(chuàng)建了converter.py。我們一步一步看完整個(gè)腳本。您可以在GitHub存儲(chǔ)庫(kù)中一次查看整個(gè)腳本。

          首先,我們導(dǎo)入所需的所有內(nèi)容并創(chuàng)建幾個(gè)常量:

          ROOT指向我們項(xiàng)目的根。因此,它是包含app的目錄。

          POSTS_DIR是以markdown編寫的文章的路徑。

          TEMPLATE_DIR分別指向?qū)?yīng)的templates目錄。

          BLOG_TEMPLATE_文件存儲(chǔ)layout.html的路徑。

          INDEX_TEMPLATE_FILE是index.html

          BASE_URL是我們項(xiàng)目的默認(rèn)地址,例如。https://florian-dahlitz.de.默認(rèn)值(如果不是通過(guò)環(huán)境變量DOMAIN提供的話)是http://0.0.0.0:5000。

          接下來(lái),我們創(chuàng)建一個(gè)名為generate_entries的新函數(shù)。這是我們定義的唯一一個(gè)轉(zhuǎn)換文章的函數(shù)。

          在函數(shù)中,我們首先獲取POSTS_DIR目錄中所有markdown文件的路徑。pathlib的awesome glob函數(shù)幫助我們實(shí)現(xiàn)它。

          此外,我們定義了Markdown包需要使用的擴(kuò)展。默認(rèn)情況下,本文中使用的所有擴(kuò)展都隨它的安裝一起提供。

          注意:您可以在文檔[4]中找到有關(guān)擴(kuò)展的更多信息。

          此外,我們實(shí)例化了一個(gè)新的文件加載程序,并創(chuàng)建了一個(gè)在轉(zhuǎn)換項(xiàng)目時(shí)使用的環(huán)境。隨后,將創(chuàng)建一個(gè)名為all_posts的空列表。此列表將包含我們處理后的所有帖子。現(xiàn)在,我們進(jìn)入for循環(huán)并遍歷POSTS_DIR中找到的所有文章。

          我們啟動(dòng)for循環(huán),并打印當(dāng)前正在處理的post的路徑。如果有什么東西出問(wèn)題了,這尤其有用。然后我們就知道,哪個(gè)文章的轉(zhuǎn)換失敗了。

          接下來(lái),我們?cè)谀J(rèn)url之后增加一部分。假設(shè)我們有一篇標(biāo)題為“面向初學(xué)者的Python”的文章。我們將文章存儲(chǔ)在一個(gè)名為python-for-beginners.md,的文件中,因此生成的url將是http://0.0.0.0:5000/posts/python-for-beginners。

          變量url_html存儲(chǔ)的字符串與url相同,只是我們?cè)谀┪蔡砑恿?html。我們使用此變量定義另一個(gè)稱為target_file.的變量。變量指向存儲(chǔ)相應(yīng)HTML文件的位置。

          最后,我們定義了一個(gè)變量md,它表示markdown.Markdown的實(shí)例,用于將markdown代碼轉(zhuǎn)換為HTML。您可能會(huì)問(wèn)自己,為什么我們沒(méi)有在for循環(huán)之前實(shí)例化這個(gè)實(shí)例,而是在內(nèi)部實(shí)例化。當(dāng)然,對(duì)于我們這里的小例子來(lái)說(shuō),這沒(méi)有什么區(qū)別(只是執(zhí)行時(shí)間稍微短一點(diǎn))。但是,如果使用諸如腳注之類的擴(kuò)展來(lái)使用腳注,則需要為每個(gè)帖子實(shí)例化一個(gè)新實(shí)例,因?yàn)槟_注添加后就不會(huì)從此實(shí)例中刪除。因此,如果您的第一篇文章使用了一些腳注,那么即使您沒(méi)有明確定義它們,所有其他文章也將具有相同的腳注。

          讓我們轉(zhuǎn)到for循環(huán)中的第一個(gè)with代碼塊。

          實(shí)際上,with代碼塊打開(kāi)當(dāng)前post并將其內(nèi)容讀入變量content。之后調(diào)用_md.convert將以markdown方式寫入的內(nèi)容轉(zhuǎn)換為HTML。隨后,env環(huán)境根據(jù)提供的模板BLOG_TEMPLATE_FILE(即layout.html如果你還記得的話)渲染生成的HTML。

          第二個(gè)with 代碼塊用于將第一個(gè)with 代碼塊中創(chuàng)建的文檔寫入目標(biāo)文件。

          以下三行代碼從元數(shù)據(jù)中獲取發(fā)布日期(被發(fā)布的日期),將其轉(zhuǎn)換為正確的格式(RFC 2822),并將其分配回文章的元數(shù)據(jù)。此外,生成的post_dict被添加到all_posts列表中。

          我們現(xiàn)在出了for循環(huán),因此,我們遍歷了posts目錄中找到的所有posts并對(duì)其進(jìn)行了處理。讓我們看看generate_entries函數(shù)中剩下的三行代碼。

          我們按日期倒序?qū)ξ恼逻M(jìn)行排序,所以首先顯示最新的文章。隨后,我們將文章寫到模板目錄一個(gè)新創(chuàng)建的index.html文件中。別把index.html錯(cuò)認(rèn)為templates/shared目錄中的那個(gè)。templates/shared目錄中的是模板,這個(gè)是我們要使用Flask服務(wù)的生成的。

          最后我們?cè)诤瘮?shù)generate_entries之后添加以下if語(yǔ)句。

          這意味著如果我們通過(guò)命令行執(zhí)行文件,它將調(diào)用generate_entries函數(shù)。

          太棒了,我們完成了converter.py腳本!讓我們從項(xiàng)目的根目錄運(yùn)行以下命令來(lái)嘗試:

          您應(yīng)該看到一些正在轉(zhuǎn)換的文件的路徑。假設(shè)您編寫了兩篇文章或使用了GitHub存儲(chǔ)庫(kù)中的兩篇文章,那么您應(yīng)該在templates目錄中找到三個(gè)新創(chuàng)建的文件。首先是index.html,它直接位于templates目錄中,其次是templates/posts目錄中的兩個(gè)HTML文件,它們對(duì)應(yīng)于markdown文件。

          最后啟動(dòng)Flask應(yīng)用程序并轉(zhuǎn)到http://0.0.0.0:5000。

          總結(jié)

          太棒了,你完成了這個(gè)系列的第一部分!在本文中,您已經(jīng)學(xué)習(xí)了如何利用Markdown包創(chuàng)建自己的Markdown to HTML生成器。您實(shí)現(xiàn)了整個(gè)管線,它是高度可擴(kuò)展的,您將在接下來(lái)的文章中看到這一點(diǎn)。

          希望你喜歡這篇文章。一定要和你的朋友和同事分享。如果你還沒(méi)有,考慮在Twitter上關(guān)注我@DahlitzF或者訂閱我的通知,這樣你就不會(huì)錯(cuò)過(guò)任何即將發(fā)表的文章。保持好奇心,不斷編碼!

          參考文獻(xiàn)

          Bootstrap (http://getbootstrap.com/)

          Primer on Jinja Templating (https://realpython.com/primer-on-jinja-templating/)

          Bootstrap Card (https://getbootstrap.com/docs/4.4/components/card/)

          Python-Markdown Extensions (https://python-markdown.github.io/extensions/)

          Tweet

          英文原文:https://florian-dahlitz.de/blog/build-a-markdown-to-html-conversion-pipeline-using-python
          譯者:阿布銩

          、Beautiful Soup簡(jiǎn)介

          爬蟲(chóng)正則表達(dá)式參考我上一篇文章:Python 爬蟲(chóng)正則表達(dá)式和re庫(kù)

          在爬蟲(chóng)過(guò)程中,可以利用正則表達(dá)式去提取信息,但是有些人覺(jué)得比較麻煩。因?yàn)榛ù罅繒r(shí)間分析正則表達(dá)式。這時(shí)候可以用高效的網(wǎng)頁(yè)解析庫(kù)Beautiful Soup。

          Beautiful Soup 是一個(gè)HTML/XML 的解析器,主要用于解析和提取 HTML/XML 數(shù)據(jù)。

          Beautiful Soup支持Python標(biāo)準(zhǔn)庫(kù)中的HTML解析器,還支持一些第三方的解析器,如果我們不安裝它,則 Python 會(huì)使用 Python默認(rèn)的解析器,lxml 解析器更加強(qiáng)大,速度更快,推薦安裝。

          下面是各種解析器優(yōu)缺點(diǎn)

          二、Beautiful Soup 安裝

          Beautiful Soup 3 目前已經(jīng)停止開(kāi)發(fā),推薦在現(xiàn)在的項(xiàng)目中使用Beautiful Soup 4,不過(guò)它已經(jīng)被移植到BS4了,也就是說(shuō)導(dǎo)入時(shí)我們需要 import bs4。

          安裝Beautiful Soup

          pip install beautifulsoup4

          根據(jù)操作系統(tǒng)不同,可以選擇下列方法來(lái)安裝lxml,安裝解析器:

          apt-get install Python-lxml
          
          easy_install lxml
          
          pip install lxml

          創(chuàng)建對(duì)象時(shí),指定解析器,這里為lxml

          from bs4 import BeautifulSoup
          
          bs = BeautifulSoup(html,"lxml")


          三、Beautiful Soup 使用

          Beautiful Soup將復(fù)雜HTML文檔轉(zhuǎn)換成一個(gè)復(fù)雜的樹(shù)形結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都是Python對(duì)象,所有對(duì)象可以歸納為4種: Tag , NavigableString , BeautifulSoup , Comment .

          (1)Tag

          標(biāo)簽,最基本的信息組織單元,分別用<>和標(biāo)明開(kāi)頭和結(jié)尾,通俗點(diǎn)講就是 HTML 中的一個(gè)個(gè)標(biāo)簽。

          Tag有很多方法和屬性,tag中最重要的屬性: name和attributes。

          name

          每個(gè)tag都有自己的名字,通過(guò) .name 來(lái)獲取:

          慣例,同樣以豆瓣電影排行做分析,鏈接為:https://movie.douban.com/top250

          import requests
          from bs4 import BeautifulSoup
          
          headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/65.0.3325.162 Safari/537.36'}
          url='https://movie.douban.com/top250'
          
          req=requests.get(url,headers=headers)
          
          html=req.text
          #print(req.text)
          
          soup=BeautifulSoup(html,'lxml')
          print(soup.h1)
          print(soup.a)

          執(zhí)行結(jié)果:

          <h1>豆瓣電影 Top 250</h1>  
          <a class="nav-login" href="https://accounts.douban.com/passport/login?source=movie" rel="nofollow">登錄/注冊(cè)</a>

          以上,就直接提取到標(biāo)簽h1和a 的內(nèi)容了,之所以只有一個(gè),因?yàn)橹惶崛?strong>第一個(gè)匹配到的內(nèi)容。

          Attributes:

          屬性,一個(gè)tag可能有很多個(gè)屬性, . tag的屬性的操作方法與字典相同。

          如上:ol class="grid_view" 的屬性, 標(biāo)簽名為ol,屬性為class,屬性值為:grid_view

          import requests
          from bs4 import BeautifulSoup
          
          headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/65.0.3325.162 Safari/537.36'}
          url='https://movie.douban.com/top250'
          
          req=requests.get(url,headers=headers)
          
          html=req.text
          #print(req.text)
          
          soup=BeautifulSoup(html,'lxml')
          #獲取標(biāo)簽div所有屬性,得到的是一個(gè)字典
          print(soup.div.attrs)
          #獲取標(biāo)簽ol屬性為class的值
          print(soup.ol['class'])

          執(zhí)行結(jié)果:

          {'id': 'db-global-nav', 'class': ['global-nav']} 
          ['grid_view']

          因?yàn)槭亲值鋵傩裕詔ag的屬性可以被添加,刪除或修改。不過(guò),對(duì)于修改刪除的操作,不是我們的主要用途,有需要的自行參考官方文檔。

          (2)NavigableString

          直譯為:可以遍歷的字符串,通過(guò)名稱可知,得到字符串。

          標(biāo)簽內(nèi)非屬性字符串,格式:soup.\<tag>.string, NavigableString可以跨越多個(gè)層次。

          如,得到了標(biāo)簽的內(nèi)容,要想獲取標(biāo)簽內(nèi)部的文字,用 .string 即可。

          上面代碼改為:

          print(soup.h1.string)

          執(zhí)行結(jié)果:

          豆瓣電影 Top 250

          (3)BeautifulSoup

          BeautifulSoup 對(duì)象表示的是一個(gè)文檔的全部?jī)?nèi)容.大部分時(shí)候,可以把它當(dāng)作 Tag 對(duì)象,是一個(gè)特殊的 Tag,我們可以分別獲取它的類型,名稱。

          print(soup.name)

          執(zhí)行結(jié)果:

          [document]

          (4)Comment

          注釋及特殊字符串,Tag , NavigableString , BeautifulSoup 幾乎覆蓋了html和xml中的所有內(nèi)容,但是還有一些特殊對(duì)象.容易讓人擔(dān)心的內(nèi)容是文檔的注釋部分:

          from bs4 import BeautifulSoup
          
          markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
          soup = BeautifulSoup(markup)
          comment = soup.b.string
          print(type(comment))
          print(comment)

          執(zhí)行結(jié)果:

          <class 'bs4.element.Comment'>
          Hey, buddy. Want to buy a used parser?

          四、遍歷文檔樹(shù)

          HTML基本格式:<>…</>構(gòu)成了所屬關(guān)系,遍歷形成了標(biāo)簽的樹(shù)形結(jié)構(gòu)。

          所以有時(shí)候不能做到一步就得到想要的元素,需要先選中一個(gè)元素再以它為基準(zhǔn)再選擇它的子節(jié)點(diǎn),父節(jié)點(diǎn),兄弟節(jié)點(diǎn)等。

          (1)子節(jié)點(diǎn)和子孫節(jié)點(diǎn)

          子節(jié)點(diǎn)屬性:.contents .children

          .content

          tag 的 .content 屬性可以將tag的子節(jié)點(diǎn)以列表的方式輸出

          import requests
          from bs4 import BeautifulSoup
          
          headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/65.0.3325.162 Safari/537.36'}
          url='https://movie.douban.com/top250'
          
          req=requests.get(url,headers=headers)
          
          html=req.text
          #print(req.text)
          
          soup=BeautifulSoup(html,'lxml')
          print(soup.ol.contents)

          執(zhí)行結(jié)果:

          ['\n', <li>
          <div class="item">
          <div class="pic">
          <em class="">1</em>
          <a href="https://movie.douban.com/subject/1292052/">
          <img alt="肖申克的救贖" class="" src="https://img9.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg" width="100"/>
          </a>
          </div> 
          <div class="info">
          ...

          輸出方式為列表,所以可以用列表索引來(lái)獲取它的某一個(gè)元素。

          print(soup.ol.contents[1])

          .children

          返回的不是一個(gè) list,不過(guò)我們可以通過(guò)遍歷獲取所有子節(jié)點(diǎn)。

          print(soup.ol.children)

          執(zhí)行結(jié)果:

          <list_iterator object at 0x7fbf14fbc4c0>

          打印輸出 .children ,可以發(fā)現(xiàn)它是一個(gè) list 生成器對(duì)象,所以需要遍歷一下獲取內(nèi)容。

          for child in soup.ol.children:
              print(child)

          輸入內(nèi)容和.content差不多。


          子孫節(jié)點(diǎn).descendants

          如果要獲得所有的子孫節(jié)點(diǎn)的話,可以調(diào)用descendants屬性,返回結(jié)果還是生成器,所以需要遍歷一下輸出可以看見(jiàn)span節(jié)點(diǎn)

          print(soup.ol.descendants)
          #print(soup.ol.children)
          for child in soup.ol.descendants:
              print(child)

          descendants會(huì)遞歸查詢所有子節(jié)點(diǎn),得到所有的子孫節(jié)點(diǎn)。

          (2)父節(jié)點(diǎn)和祖父節(jié)點(diǎn)

          獲取父節(jié)點(diǎn).parent

          print(soup.ol.parent)

          獲取所有祖先節(jié)點(diǎn),同理需要遍歷獲取。

          for parent in soup.ol.parents:
              print(parent)

          (3)兄弟節(jié)點(diǎn)

          獲取兄弟節(jié)點(diǎn):.next_sibling .previous_sibling

          next_sibling和previous_sibling分別獲取節(jié)點(diǎn)的下一個(gè)和上一個(gè)兄弟元素。

          print(soup.li.next_sibling)
          print(soup.li.previous_sibling)

          如果節(jié)點(diǎn)不存在,則返回 None,實(shí)際中通常是字符串或空白,因?yàn)榭瞻谆蛘邠Q行也可以被視作一個(gè)節(jié)點(diǎn),所以得到的結(jié)果可能是空白或者換行。


          全部兄弟節(jié)點(diǎn):next_siblings previous_siblings

          分別返回后面和前面的兄弟節(jié)點(diǎn),同理,所有節(jié)點(diǎn)需要遍歷獲得。

          print(soup.li.next_siblings)
          print(soup.li.previous_siblings)
          
          for sibling in soup.li.next_siblings:
              print(sibling)
          for previous in soup.li.previous_siblings:
              print(previous)


          (4)回退和前進(jìn)節(jié)點(diǎn)

          前后節(jié)點(diǎn):.next_element .previous_element

          與 .next_sibling .previous_sibling 不同,它并不是針對(duì)于兄弟節(jié)點(diǎn),而是在所有節(jié)點(diǎn),不分層次

          比如 head 節(jié)點(diǎn)為

          <head><title>The Dormouse's story</title></head>

          那么它的下一個(gè)節(jié)點(diǎn)便是 title,它是不分層次關(guān)系的。

          所有前后節(jié)點(diǎn):.next_elements .previous_elements

          同理,返回的是迭代器,需要遍歷獲得。

          五、搜索文檔樹(shù)

          eautiful Soup定義了很多搜索方法,主要用的2個(gè)方法:find() 和 find_all()

          (1)find_all

          語(yǔ)法:find_all(name, attrs, recursive, text, **kwargs)

          name:

          我們可以根據(jù)節(jié)點(diǎn)名來(lái)查詢?cè)亍ame可以是:字符串、正則表達(dá)式、列表、True、方法

          print(soup.find_all('a'))

          因?yàn)槭荰ag類型,我們可以進(jìn)行嵌套查詢.

          for a in soup.find_all('a'):
              print(a.find_all('span'))
              print(a.string)

          attrs

          除了根據(jù)節(jié)點(diǎn)名查詢的話,同樣的也可以通過(guò)屬性來(lái)查詢。

          print(soup.find_all(attrs={'id': 'link1'}))
          print(soup.find_all(attrs={'name': 'Dormouse'}))

          常用的屬性比如class,我們可以直接傳入class這個(gè)參數(shù)。在這里需要注意的是class是Python的保留字,所以在class的后面加上下劃線。

          print(soup.find_all(class_="title"))

          執(zhí)行結(jié)果:

          <span class="title">肖申克的救贖</span>, <span class="title"> / The Shawshank Redemption</span>, <span class="title">霸王別姬</span>, <span class="title">阿甘正傳</span>

          (2)find

          除了find_all( )方法,還有find( )方法,前者返回的是多個(gè)元素,以列表形式返回,后綴是返回一個(gè)元素。即第一個(gè)元素。

          find( )與find_all( )的使用方法相同。

          find_parents() 和find_parent():前者返回所有祖先節(jié)點(diǎn),后者返回直接父節(jié)點(diǎn)。

          find_next_siblings()和find_next_sibling():前者返回后面的所有兄弟節(jié)點(diǎn),后者返回后面第一個(gè)兄弟節(jié)點(diǎn)。

          find_previous_siblings和find_previous_sibling():前者返回前面的所有兄弟節(jié)點(diǎn),后者返回前面第一個(gè)兄弟節(jié)點(diǎn)。

          六、CSS選擇器

          Beautiful Soup還提供了另一種選擇器,即CSS選擇器。

          soup.select(),返回類型是 list。


          同樣可以用 標(biāo)簽名、類名、 id 名、組合、屬性查找。

          (1)soup.select()

          獲取title標(biāo)簽節(jié)點(diǎn)
          print(soup.select('title'))
          
          獲取class為title的節(jié)點(diǎn)
          print(soup.select('.title'))
          
          獲取li標(biāo)簽下的a節(jié)點(diǎn)
          print(soup.select('li a'))
          
          查找時(shí)還可以加入屬性元素,屬性需要用中括號(hào)括起來(lái),
          注意屬性和標(biāo)簽屬于同一節(jié)點(diǎn),所以中間不能加空格,否則會(huì)無(wú)法匹配到
          print soup.select('a[href="http://example.com/elsie"]')

          (2)嵌套選擇

          同樣可以使用嵌套查詢

          for ul in soup.select('ul'):
              print(ul.select('li'))

          (3)獲取屬性

          for ul in soup.select('ul'):
              print(ul['id'])
              print(ul.attrs['id'])

          (4)獲取文本

          for li in soup.select('li'):
              print('String:', li.string)
              print('get text:', li.get_text())


          七、獲取豆瓣電影排行首頁(yè)影片信息

          通過(guò)以上的方法,現(xiàn)在獲取豆瓣電影排行首頁(yè)的排名、電影名、導(dǎo)演演員、年份類型。

          從頁(yè)面分析,所有影片信息,在class標(biāo)簽值為grid_view的里面。

          所以第一步獲取所有g(shù)rid_view里面所有l(wèi)i標(biāo)簽的值,返回的是一個(gè)列表。

          list= soup.find(class_='grid_view').find_all('li')

          排序,在列表每個(gè)元素中,獲取em標(biāo)簽值,為排序,只取字符串。

          find('em').string

          電影名稱,獲取第一個(gè)title值

          find(class_='title').string

          導(dǎo)演和年代信息,在標(biāo)簽p當(dāng)中,獲取的是text文本格式。由于中間有空格,還有br換行符,所以最后還需要replace替換掉。

          item.find('p').text.replace(' ','')

          最終代碼為:

          url='https://movie.douban.com/top250'
          
          req=requests.get(url,headers=headers)
          
          html=req.text
          #print(req.text)
          soup=BeautifulSoup(html,'lxml')
          
          list= soup.find(class_='grid_view').find_all('li')
          
          for item in list:
              item_num=item.find('em').string
              item_name=item.find(class_='title').string
              item_act=item.find('p').text.replace(' ','')
              print("排名:"+item_num,"\n電影名稱:"+item_name,item_act)

          執(zhí)行結(jié)果:

          排名:1 
          電影名稱:肖申克的救贖 
          導(dǎo)演:弗蘭克·德拉邦特FrankDarabont   主演:蒂姆·羅賓斯TimRobbins/...
          1994 / 美國(guó) / 犯罪劇情
          
          排名:2 
          電影名稱:霸王別姬 
          導(dǎo)演:陳凱歌KaigeChen   主演:張國(guó)榮LeslieCheung/張豐毅FengyiZha...
          1993 / 中國(guó)大陸中國(guó)香港 / 劇情愛(ài)情同性
          
          排名:3 
          電影名稱:阿甘正傳 
          導(dǎo)演:羅伯特·澤米吉斯RobertZemeckis   主演:湯姆·漢克斯TomHanks/...
          1994 / 美國(guó) / 劇情愛(ài)情
          
          排名:4 
          電影名稱:泰坦尼克號(hào) 
          導(dǎo)演:詹姆斯·卡梅隆JamesCameron   主演:萊昂納多·迪卡普里奧Leonardo...
          1997 / 美國(guó)墨西哥澳大利亞加拿大 / 劇情愛(ài)情災(zāi)難
          
          排名:5 
          電影名稱:這個(gè)殺手不太冷 
          導(dǎo)演:呂克·貝松LucBesson   主演:讓·雷諾JeanReno/娜塔莉·波特曼...
          1994 / 法國(guó)美國(guó) / 劇情動(dòng)作犯罪
          
          排名:6 
          電影名稱:美麗人生 
          導(dǎo)演:羅伯托·貝尼尼RobertoBenigni   主演:羅伯托·貝尼尼RobertoBeni...
          1997 / 意大利 / 劇情喜劇愛(ài)情戰(zhàn)爭(zhēng)
          
          排名:7 
          電影名稱:千與千尋 
          導(dǎo)演:宮崎駿HayaoMiyazaki   主演:柊瑠美RumiH?ragi/入野自由Miy...
          2001 / 日本 / 劇情動(dòng)畫奇幻
          
          排名:8 
          電影名稱:辛德勒的名單 
          導(dǎo)演:史蒂文·斯皮爾伯格StevenSpielberg   主演:連姆·尼森LiamNeeson...
          1993 / 美國(guó) / 劇情歷史戰(zhàn)爭(zhēng)
          
          排名:9 
          電影名稱:盜夢(mèng)空間 
          導(dǎo)演:克里斯托弗·諾蘭ChristopherNolan   主演:萊昂納多·迪卡普里奧Le...
          2010 / 美國(guó)英國(guó) / 劇情科幻懸疑冒險(xiǎn)
          
          排名:10 
          電影名稱:星際穿越 
          導(dǎo)演:克里斯托弗·諾蘭ChristopherNolan   主演:馬修·麥康納MatthewMc...
          2014 / 美國(guó)英國(guó)加拿大 / 劇情科幻冒險(xiǎn)
          
          排名:11 
          電影名稱:忠犬八公的故事 
          導(dǎo)演:萊塞·霍爾斯道姆LasseHallstr?m   主演:理查·基爾RichardGer...
          2009 / 美國(guó)英國(guó) / 劇情
          
          排名:12 
          電影名稱:楚門的世界 
          導(dǎo)演:彼得·威爾PeterWeir   主演:金·凱瑞JimCarrey/勞拉·琳妮Lau...
          1998 / 美國(guó) / 劇情科幻
          
          排名:13 
          電影名稱:海上鋼琴師 
          導(dǎo)演:朱塞佩·托納多雷GiuseppeTornatore   主演:蒂姆·羅斯TimRoth/...
          1998 / 意大利 / 劇情音樂(lè)
          
          排名:14 
          電影名稱:三傻大鬧寶萊塢 
          導(dǎo)演:拉庫(kù)馬·希拉尼RajkumarHirani   主演:阿米爾·汗AamirKhan/卡...
          2009 / 印度 / 劇情喜劇愛(ài)情歌舞
          
          排名:15 
          電影名稱:機(jī)器人總動(dòng)員 
          導(dǎo)演:安德魯·斯坦頓AndrewStanton   主演:本·貝爾特BenBurtt/艾麗...
          2008 / 美國(guó) / 科幻動(dòng)畫冒險(xiǎn)
          
          排名:16 
          電影名稱:放牛班的春天 
          導(dǎo)演:克里斯托夫·巴拉蒂ChristopheBarratier   主演:讓-巴蒂斯特·莫尼...
          2004 / 法國(guó)瑞士德國(guó) / 劇情喜劇音樂(lè)
          
          排名:17 
          電影名稱:無(wú)間道 
          導(dǎo)演:劉偉強(qiáng)/麥兆輝   主演:劉德華/梁朝偉/黃秋生
          2002 / 中國(guó)香港 / 劇情犯罪驚悚
          
          排名:18 
          電影名稱:瘋狂動(dòng)物城 
          導(dǎo)演:拜倫·霍華德ByronHoward/瑞奇·摩爾RichMoore   主演:金妮弗·...
          2016 / 美國(guó) / 喜劇動(dòng)畫冒險(xiǎn)
          
          排名:19 
          電影名稱:大話西游之大圣娶親 
          導(dǎo)演:劉鎮(zhèn)偉JeffreyLau   主演:周星馳StephenChow/吳孟達(dá)ManTatNg...
          1995 / 中國(guó)香港中國(guó)大陸 / 喜劇愛(ài)情奇幻古裝
          
          排名:20 
          電影名稱:熔爐 
          導(dǎo)演:黃東赫Dong-hyukHwang   主演:孔侑YooGong/鄭有美Yu-miJung/...
          2011 / 韓國(guó) / 劇情
          
          排名:21 
          電影名稱:控方證人 
          導(dǎo)演:比利·懷爾德BillyWilder   主演:泰隆·鮑華TyronePower/瑪琳·...
          1957 / 美國(guó) / 劇情犯罪懸疑
          
          排名:22 
          電影名稱:教父 
          導(dǎo)演:弗朗西斯·福特·科波拉FrancisFordCoppola   主演:馬龍·白蘭度M...
          1972 / 美國(guó) / 劇情犯罪
          
          排名:23 
          電影名稱:當(dāng)幸福來(lái)敲門 
          導(dǎo)演:加布里爾·穆奇諾GabrieleMuccino   主演:威爾·史密斯WillSmith...
          2006 / 美國(guó) / 劇情傳記家庭
          
          排名:24 
          電影名稱:觸不可及 
          導(dǎo)演:奧利維·那卡什OlivierNakache/艾力克·托蘭達(dá)EricToledano   主...
          2011 / 法國(guó) / 劇情喜劇
          
          排名:25 
          電影名稱:怦然心動(dòng) 
          導(dǎo)演:羅伯·萊納RobReiner   主演:瑪?shù)铝铡た_爾MadelineCarroll/卡...
          2010 / 美國(guó) / 劇情喜劇愛(ài)情


          八、小結(jié)

          推薦使用lxml解析庫(kù),必要時(shí)選擇html.parser。相對(duì)于正則表達(dá)式,Beautiful Soup更加簡(jiǎn)單,但是網(wǎng)上有些推薦正則表達(dá)式,理由是精確。

          具體用哪個(gè),還是根據(jù)環(huán)境選擇吧,一起使用都可以。


          主站蜘蛛池模板: 国产精品无码一区二区三级 | 精品视频一区二区三区在线观看 | 精品福利一区二区三| 伊人无码精品久久一区二区| 日韩精品乱码AV一区二区| 美女啪啪一区二区三区| 亚洲av无码一区二区三区不卡| 国模无码人体一区二区| 日本不卡一区二区三区视频| 另类免费视频一区二区在线观看| 国产一区二区在线看| 免费无码毛片一区二区APP| 无码人妻精品一区二区在线视频 | 一区二区三区四区精品视频 | 国产av福利一区二区三巨| 一区二区视频免费观看| 亚洲av高清在线观看一区二区 | 台湾无码一区二区| 日本激情一区二区三区| 亚洲av乱码中文一区二区三区| 亚洲乱色熟女一区二区三区蜜臀| 欧美日韩综合一区二区三区| 久久精品无码一区二区三区不卡| 国产人妖视频一区在线观看| 一区二区三区日本电影| 日本一区精品久久久久影院| 亚洲一区二区三区播放在线| 天天视频一区二区三区| 亚洲日本一区二区三区在线不卡| 在线精品亚洲一区二区三区| 久久免费区一区二区三波多野| 亚拍精品一区二区三区| 日韩在线视频一区二区三区| 国产精品小黄鸭一区二区三区| 无码一区二区三区免费| 亚州AV综合色区无码一区| 天码av无码一区二区三区四区| 久久精品一区二区国产| 亚洲欧美日韩一区二区三区在线| 日韩精品一区二区三区在线观看| 日本一区二区三区精品中文字幕|