考慮到現在大部分小伙伴使用 Python 主要因為爬蟲,那么為了更好地幫助大家鞏固爬蟲知識,加深對爬蟲的理解,選擇了爬取百度文庫作為我們的目標。
廢話不多說,我們開始。
TXT、DOCX 爬取與保存
在爬取任何東西之前,我們都要先確認需要爬取的數據是不是異步加載的;如果是異步加載的直接爬取網頁是爬不到的。
要知道是不是異步加載其實很簡單,就用requests 對網頁發起請求,看看 response 是什么就可以了
url = 'https://wenku.baidu.com/view/4e29e5a730126edb6f1aff00bed5b9f3f90f72e7.html?rec_flag=default'
header = {'User-agent': 'Mozilla/5.0 (windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'}
res = requests.get(url , headers = header)
res.text
很明顯,返回的東西,并不是我們所需要的內容; 根據常理來說,我們就可以認為該網頁是異步加載的。
但是,從常識來講,如果網頁的內容是異步加載的,那么直接通過百度搜索,是搜索不到網頁內部的內容的,但是很顯然,我們每次通過百度搜索都是可以直接找到文庫中的文本內容的。
如下:
那么這就有意思了,明明直接發起請求是獲取不到網頁內容的,但是為什么通過百度搜索就可以找到呢? 關鍵肯定在于百度搜索 上面。
這個時候通過查閱資料,我們了解到,最主要的問題出在我們的 headers
在爬取網頁時,headers 通常是作為身份證,讓網頁不看出我們是爬蟲;如果不加 headers,網頁直接就會看出我們是爬蟲,就會拒絕訪問
再深入了解一下 headers 的識別機理,我們發現了叫做 Robot 協議的東西。
它規定了什么樣的 headers 可以訪問網頁內部內容,除了指定 headers 之外的 headers,都是無法請求頁面內容的。
比如說百度文庫的 Robot 協議就是下面這樣的:
User-agent: Baiduspider
Disallow: /w?
Disallow: /search?
Disallow: /submit
Disallow: /upload
Disallow: /cashier/
而我們需要爬取的內容 url 格式為
https://wenku.baidu.com/view/?.html
這代表 Baiduspider 應該可以爬取文庫內容;大致猜測這是因為百度搜索時需要根據文本內容匹配搜索選項,所以放行。
因此我們嘗試偽裝 UA 為 Baiduspider
url = 'https://wenku.baidu.com/view/4e29e5a730126edb6f1aff00bed5b9f3f90f72e7.html?rec_flag=default'
header = {'User-agent': 'googlebot'}
res = requests.get(url , headers = header)
res.text
果然不出所料,我們成功地獲取到了目標內容
既然已經成功獲取到了網頁的正確源代碼,那么下一步就是去解析網頁獲取內容。
解析網頁源代碼的庫有很多,這里我們使用 BeautifulSoup
plist = []
soup = BeautifulSoup(r, "html.parser")
plist.append(soup.title.string)
for div in soup.find_all('div', attrs={"class": "bd doc-reader"}):
plist.extend(div.get_text().split('n'))
plist = [c.replace(' ', '') for c in plist]
plist = [c.replace('\x0c', '') for c in plist]
plist
整個解析是非常容易的,都是很標準的操作。
在這里就不多加敘述了,最終的效果如下:
當然爬取到東西了只是萬里長征的第一步,就這樣是肯定不行的,我們還需要將爬取的內容保存起來,通常是保存為 txt 文件
file = open('test.txt', 'w',encoding='utf-8')
for str in plist:
file.write(str)
file.write('n')
file.close()
但是為了美觀起見,我們在這里選擇使用 python-docx 庫將內容保存為 docx 文件
with open('test.txt', encoding='utf-8') as f:
docu = Document()
docu.add_paragraph(f.read())
docu.save('test.docx')
PPT、PDF 爬取與保存
有了之前的經驗教訓,在爬取的時候我們首先就嘗試了使用爬取 TXT、DOCX 的方法,嘗試是否可以爬到內容
url = 'https://wenku.baidu.com/view/a4ac1b57dd88d0d232d46a0f.html?fr=search'
header = {'User-agent': 'Googlebot'}
res = requests.get(url , headers = header)
res.text
很可惜的是,我們并沒有訪問到
原因仔細想想也很簡單,在百度搜索的時候,直接搜索是搜不到 PPT 或者 PDF 的內容的
那么很顯然,PPT 和 PDF 是通過異步的方法進行內容加載的
對待異步加載的數據,我們通常采取的策略有兩種
1、第一個就是直接找到發起異步請求的接口,自己構造請求頭,發起請求
2、第二個就是通過 Selenium 這樣的自動化測試工具去爬取
百度文庫的接口太難找了,請求頭的構造也很麻煩,找了很久也沒有很滿意。
所以在本次爬取中,我們使用的是第二種方法,使用 Selenium 這樣的自動化測試工具
這里我們需要下載 ChromeDriver 這個插件,當然這里是默認大家使用的是 Chrome 瀏覽器,如果是其他的瀏覽器,firefox,safari 等等,直接去網上找到相應 Driver 就可以了。
這里給出 ChromeDriver 的下載地址
http://npm.taobao.org/mirrors/chromedriver/
大家一定要下載和自己 Chrome 瀏覽器版本一致的 ChromeDriver,不然程序是運行會報錯
我們先不急著馬上開始爬取,我們先來嘗試使用一下Selenium調用ChromeDriver
import requests
from selenium import webdriver
url = 'https://wenku.baidu.com/view/5292b2bc0166f5335a8102d276a20029bd64638c.html?fr=search'
driver = webdriver.Chrome(r'F:driverchromedriver.exe')
driver.get(url)
怎么樣,是不是瀏覽器自動打開了? 現在我們嘗試輸出這個 driver,就可以看見,網頁的正確源代碼已經在里面了
現在我們仔細研究一下源代碼就可以看到,我們需要的內容在下面這個位置
現在正確的源代碼也有了,內容的位置也知道了,直接解析,爬取,完事就好了。
想得美,經過這樣的爬取之后,對內容進行解析,讓我們看看究竟爬到沒有
from lxml import etree
import re
html=etree.HTML(driver.page_source)
links=html.xpath("//div[@class='reader-pic-item']/@style")
part = re.compile(r'url[(](.*?)[)]')
qa="".join(links)
z=part.findall(qa)
z
我們可以知道,其實我們只爬到 3 張 PDF,其他的都沒有爬到。
這是為什么呢?
這是百度文庫為了防止大家去爬,專門設置的一個小機關
返回百度文庫,我們仔細看看源代碼,其實我們可以發現,隨著頁面的變化,源代碼是不斷改變的,每次都只有 3 張圖片的 url。
并且這個頁碼數也有一定的規律,如果在第二頁,那么圖片就是 1,2,3,如果在第三頁,圖片就是 2,3,4
那么我們的疑惑一下就解決了,只需要不斷地進行換頁的爬取,就可以了
接下來就是如何實現換頁的操作了
這個需要兩個步驟,先是點擊繼續閱讀,然后進行頁面輸入實現換頁。
先實現點擊的操作,代碼如下:
button = driver.find_element_by_xpath("//*[@id='html-reader-go-more']/div[2]/div[1]/span")
button.click()
driver.execute_script("arguments[0].click();", button)
整個操作是通過 JS 來進行的,大家可以把這個記住,以后需要點擊的時候直接用就可以
然后就是輸入頁面實現換頁,這個其實涉及的比較多,細分的話,步驟分為獲取總頁數,依次輸入頁面并點擊。
import re
# 尋找頁面
source = re.compile(r'<span class="page-count">/(.*?)</span>')
number = int(source.findall(driver.page_source)[0])
# 輸入頁面并點擊
driver.find_element_by_class_name("page-input").clear()
driver.find_element_by_class_name("page-input").send_keys('2')
driver.find_element_by_class_name("page-input").send_keys(Keys.ENTER)
如果小伙伴成功實現了上面的操作,其實大體的爬取工作已經差不多了,接下來就是保存我們的 PPT 和 PDF了
因為爬取 PDF 和 PPT 的時候,我們是爬取的圖片的源地址,那么我們要獲得這張圖片并保存下來就必須對這個地址發起請求,然后將返回頭以二進制保存下來。
for m in range(3):
pic = requests.get(z[m]).content
# 方法一
# file = open(f'./照片/{m+1}.jpg','wb')
# file.write(pic)
# file.close()
# 方法二
with open(f'./照片/{m+1}.jpg','wb') as f:
f.write(pic)
f.close()
在這里,提醒大家一下一定要按照對圖片用正確順序進行命名,因為后面保存為 PDF 的時候,需要排序
在 py 文件的目錄下,大家就可以看見保存下來的圖片了
最后一步,將圖片保存為 PDF
from PIL import Image
import os
folderPath = "F:/TEST"
filename = "test"
files = os.listdir(folderPath)
jpgFiles = []
sources = []
for file in files:
if 'jpg' in file:
jpgFiles.append(file)
tep = []
for i in jpgFiles:
ex = i.split('.')
tep.append(int(ex[0]))
tep.sort()
jpgFiles=[folderPath +'/'+ str(i) + '.jpg' for i in tep]
output = Image.open(jpgFiles[0])
jpgFiles.pop(0)
for file in jpgFiles:
img = Image.open(file)
img = img.convert("P")
sources.append(img)
output.save(f"./{filename}.pdf","PDF",save_all=True,append_images=sources)
最終的結果就是生成了咱們的 PDF 文件
上述的操作看起來很多,很麻煩,其實并不是的,因為大部分的操作都是固定的,大家只需要記熟就可以了。
簡單的 GUI 制作
GUI 這塊,我們整了點新活兒,用 C# 編寫 winform 簡易的 GUI,調用爬蟲的 Py thon 代碼
C# 調用 python 項目的方式我簡單用 Process 類執行,通過執行 python.exe 執行代碼
public static void RunPythonScript(string sArgName, string py, string args = "", params string[] teps)
{
Process p = new Process();
//(沒放debug下,寫絕對路徑)
//string path = @"C:UserszllDesktopbaiduCSharpCallPythonbinDebug" + sArgName;
// 獲得python文件的絕對路徑(將文件放在c#的debug文件夾中可以這樣操作)
string path = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + sArgName;
//沒有配環境變量的話,可以像我這樣寫python.exe的絕對路徑。如果配了,直接寫"python.exe"即可
//p.StartInfo.FileName = @"C:UserszllAppDataLocalProgramsPythonPython37-32python.exe";
p.StartInfo.FileName = @py;
string sArguments = path;
foreach (string sigstr in teps)
{
sArguments += " " + sigstr;//傳遞參數
}
sArguments += " " + args;
p.StartInfo.Arguments = sArguments;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();
p.BeginOutputReadLine();
p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
Console.ReadLine();
p.WaitForExit();
}
對輸入 的內容進行簡單判斷,如果不是百度文庫地址或不是 python.exe 地址,報錯
if (!url.Contains("https://wenku.baidu.com/view/"))
{
MessageBox.Show("請輸入正確的百度文庫網址!");
lbl_state.Text = "請重新輸入。";
}
else if (!py.Contains("python.exe"))
{
MessageBox.Show("請輸入正確的python.exe路徑!");
lbl_state.Text = "請重新輸入。";
}
else
{
//輸入參數列表
String[] strArr = new String[] { url, istxt };
string sArguments = @"srcwenku.py";//這里是python的文件名字
RunPythonScript(sArguments, py, "-u", strArr);
if(result.Contains("0"))
lbl_state.Text = "對不起,爬取失敗。";
else if (result.Contains("1"))
lbl_state.Text = "爬取成功!";
}
因為 GUI 部分比較簡單,這里就不過多描述了。