1.動機
對于知乎的一些高知大V,他們的回答總是那么具有說服力,通過閱讀他們的回答,了解他們對熱點事件的分析方式,通過現象看本質,一不至于被帶節奏,二增加自己的知識面。多讀多看,大有裨益。那如果在網絡信號不太好或不舍得太多流量的情況下(窮),能夠翻看他們的回答就太好了。
本篇介紹一下如何把”惡喵的奶爸“知乎回答頁全部下載下來并保存為一個PDF。
1.1.分析
實現方式一,獲取全部HTML源代碼,將多個HTML文件合成一個HTML文件,將最后合成的這個文件保存為PDF。
實現方式二,將單個HTML文件保存為PDF,再將多個PDF合成一個。
經分析,后者更容易實現。
在正式爬之前,多做一些本地的測試,在本地能夠行得通,再去騷擾目標網站。這樣做的目的,一是不讓網站運營者惡心;二是節約自己的時間和精力,因為大型網站大多有自己的反爬措施,頻繁騷擾兩三次,ip就被封了,那還要考慮換ip等一系列問題。
2.將本地HTML保存為PDF文件
先用selenium訪問以下目標網站,將源代碼保存到本地HTML,然后用本地的HTML做測試。
# -*- coding: utf-8 -*-
# @AuThor : frank_lee
import pdfkit
htmlfile = open("zhihu_answer.html", 'r', encoding='utf-8')
confg = pdfkit.configuration(wkhtmltopdf=r'D:\htmlpdf\wkhtmltopdf\bin\wkhtmltopdf.exe')
pdfkit.from_url(htmlfile, 'zhihu.pdf', configuration=confg)
2.1.上面代碼能夠正常執行的先決條件--安裝wkhtmltopdf、pdfkit
2.1.1.先安裝wkhtmltopdf,這個工具的下載網站是:https://wkhtmltopdf.org/downloads.html
根據自己的操作系統下載對應的版本即可。安裝完成后可以將其加入到環境變量中,也可以不加入,但每次使用時需要調用wkhtmltopdf.exe的絕對路徑。
2.2.2.安裝pdfkit模塊
pip install pdfkit
3.將一個本地HTML文件保存為多個PDF文件
import pdfkit
import time
i = 0
while i < 4:
# pdfname = "zhihu{}".format(i)+".pdf"
htmlfile = open("zhihu_answer.html", 'r', encoding='utf-8')
confg = pdfkit.configuration(wkhtmltopdf=r'D:\htmlpdf\wkhtmltopdf\bin\wkhtmltopdf.exe')
pdfkit.from_url(htmlfile, "zhihu{}".format(i)+".pdf", configuration=confg)
i += 1
time.sleep(10)
while循環和for循環都可以實現,但是如果這樣寫,只會保存為一個完整的PDF文件,剩下的都是空白PDF:
# -*- coding: utf-8 -*-
# @AuThor : frank_lee
import pdfkit
import time
htmlfile = open("zhihu_answer.html", 'r', encoding='utf-8')
confg = pdfkit.configuration(wkhtmltopdf=r'D:\htmlpdf\wkhtmltopdf\bin\wkhtmltopdf.exe')
i = 0
while i < 4:
# pdfname = "zhihu{}".format(i)+".pdf"
pdfkit.from_url(htmlfile, "zhihu{}".format(i)+".pdf", configuration=confg)
i += 1
time.sleep(10)
所以要注意代碼的執行順序。
4.將多個PDF文件合成一個PDF文件
# -*- coding: utf-8 -*-
# @AuThor : frank_lee
import os, PyPDF2
# 找出所有的pdf文件,并將文件名保存至列表。
filelist = []
for filename in os.listdir('./dir-with-pdfs'):
if filename.endswith('.pdf'):
filelist.append(filename)
# 創建一個新的pdf
newPdfFile = PyPDF2.PdfFileWriter()
# 循環打開每一個pdf文件,將內容添加至新的pdf
for filename in filelist:
pdfFile = open('./dir-with-pdfs/' + filename, 'rb')
pdfObj = PyPDF2.PdfFileReader(pdfFile)
# 獲取頁數
pageNum = pdfObj.numPages
for num in range(0, pageNum):
pageContent = pdfObj.getPage(num)
newPdfFile.addPage(pageContent)
newFile = open('zhihu_emiao.pdf', 'wb')
newPdfFile.write(newFile)
newFile.close()
5.動真格的
在上一篇的基礎上,添加登錄功能,因為知乎有些有些大V的回答頁面不登錄就能訪問,有些則不行。
5.1.模擬登錄
如果能夠避開驗證碼實現登錄,豈不是很輕松,哎,想一下還蠻激動,試一下使用selenium結合瀏覽器開發者模式還真可以,關鍵代碼如下:
options = webdriver.ChromeOptions()
options.add_experimental_option(
'excludeSwitches', ['enable-automation'])
self.browser = webdriver.Chrome(options=options)
在該模式下,可以完美避開驗證碼,直接輸入用戶名和密碼就能實現登錄。如果用戶名和密碼還要手動輸入就太low了。此時,selenium可能會說,兄弟,顯示等待了解一下。這里的代碼帶有“self”,因為完整代碼是包含一個zhihu_infos類。如果直接定義一個函數,或者隨心所欲,不定義函數,想到哪兒是哪兒,可以將其刪掉。下面的代碼用到了顯示等待,顯示等待是針對于某個特定的元素設置的等待時間,如果在規定的時間范圍內,沒有找到元素,則會拋出異常,如果在規定的時間內找到了元素,則直接執行,即找到元素就執行相關操作。WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XX, 'XX')))
既然有until,自然也有until_not
WebDriverWait()一般由until()或 until_not()方法配合使用
until(method, message=' '):調用該方法提供的驅動程序作為一個參數,直到返回值為True
until_not(method, message=' '):調用該方法提供的驅動程序作為一個參數,直到返回值為False
# 等待 登錄選項 出現,并點擊
password_login = self.wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '.SignContainer-switch > span:nth-child(1)')))
password_login.click()
time.sleep(3)
# 等待 賬號 出現
zhihu_user = self.wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '.SignFlow-accountInput > input:nth-child(1)')))
zhihu_user.send_keys(zhihu_username)
# 等待 密碼 出現
zhihu_pwd = self.wait.until(
EC.presence_of_element_located(
(By.CSS_SELECTOR,
'.SignFlow-password > div:nth-child(1) > div:nth-child(1) > input:nth-child(1)')))
zhihu_pwd.send_keys(zhihu_password)
# 等待 登錄按鈕 出現
submit = self.wait.until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, 'button.Button:nth-child(5)')))
submit.click()
time.sleep(10)
5.2.實現點擊
和上篇一樣,不點擊,是無法查看完整回答的,不同點是這個回答頁面共有20個回答,上篇是10個。
實現點擊并返回網頁源代碼:
def get_pagesource(self, url):
self.browser.get(url=url)
self.browser.maximize_window()
time.sleep(5)
# 執行點擊動作
for j in range(1, 21):
content_click = '#Profile-answers > div:nth-child(2) > div:nth-child(' + str(
j) + ') > div > div.RichContent.is-collapsed > div.RichContent-inner'
try:
complete_content = self.wait.until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, content_click)))
complete_content.click()
time.sleep(1)
except BaseException:
pass
pagedata = self.browser.page_source
return pagedata
5.3.將網頁源代碼以.html的格式保存到本地
def save_to_html(self, base_file_name, pagedata):
filename = base_file_name + ".html"
with open(self.html_path + filename, "wb") as f:
f.write(pagedata.encode("utf-8", "ignore"))
f.close()
return filename
5.4.將已保存的HTML保存為PDF格式
def html_to_pdf(self, base_file_name, htmlname):
pdfname = base_file_name + ".pdf"
htmlfile = open(self.html_path+htmlname, 'r', encoding='utf-8')
confg = pdfkit.configuration(
wkhtmltopdf=r'D:\htmlpdf\wkhtmltopdf\bin\wkhtmltopdf.exe')
pdfkit.from_url(htmlfile, self.pdf_path + pdfname, configuration=confg)
5.5.將多個PDF文件保存為一個
def Many_to_one(self):
# 找出所有的pdf文件,并將文件名保存至列表。
filelist = []
for filename in os.listdir('./pdf_file'):
if filename.endswith('.pdf'):
filelist.append(filename)
# 創建一個新的pdf
newPdfFile = PyPDF2.PdfFileWriter()
# 循環打開每一個pdf文件,將內容添加至新的pdf
for filename in filelist:
pdfFile = open('./pdf_file/' + filename, 'rb')
pdfObj = PyPDF2.PdfFileReader(pdfFile)
# 獲取頁數
pageNum = pdfObj.numPages
for num in range(1, pageNum):
pageContent = pdfObj.getPage(num)
newPdfFile.addPage(pageContent)
newFile = open(self.pdf_path+'惡喵的奶爸.pdf', 'wb')
newPdfFile.write(newFile)
newFile.close()
6.完整代碼
完整代碼實現了將多頁回答HTML格式保存為一個文件夾,PDF格式的保存到另外一個文件夾,最后將多個PDF文件合成一個PDF文件。由于代碼行數較多,貼在這里不太美觀,如有需要請查看github