32.使用selenium爬取知乎,并实现多页保存为一个PDF文
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