上一篇《Python 爬蟲實戰: 爬取人民日報新聞文章》發布之後,確實幫到了不少朋友。
這篇文章我們爬取《解放日報》新聞文章,同樣是面向爬蟲新手的零基礎教程。
一、分析網站首先應該 明確需求,就是要明確我們需要什麼樣的數據,以什麼樣的形式來保存等等。不過我們這個爬蟲需求跟《人民日報》爬蟲一致,這部分暫且略過,我們直接來分析網站。
解放日報的訪問地址如下:
https://www.shobserver.com/staticsg/res/html/journal/index.html?date=2021-12-22&page=01
其主界面跟 人民日報 網站非常相似,都是 版面列表 -- 文章列表 -- 文章詳情 的這種結構。
不過簡單分析後發現,這個網站的新聞數據是通過 Ajax 動態加載出來的(人民日報的數據是靜態加載的),也就是說,我們無法直接解析 html 源碼獲得數據。
1.1 數據動態加載是怎麼回事兒很多剛接觸爬蟲的同學,習慣上來 F12 打開開發者工具,然後就咔咔定位數據找標籤。如果是像人民日報那樣的靜態網頁還好,你分析時看到的標籤是什麼樣子,用代碼爬的時候基本上也是那個樣子。但是遇到解放日報這種 動態加載 的網站,就直接懵逼了,明明我標籤位置,名字,class 和 id 什麼的都沒寫錯,為什麼爬取的時候就總是報錯說找不到標籤呢?答案就是,你找的那個標籤是動態生成的,原始網頁源碼裡根本沒有,當然找不到了。
大家看下面,這是解放日報的版面導航列表。
一般大家爬取的時候,會先找到這個 <div> 標籤,然後在這個標籤下找到所有的 <dd> 標籤,然後再找 <a> 標籤,然後就找到了想要的數據。
然鵝,當我們打開查看網頁源碼的時候(chrome 瀏覽器為例,滑鼠右鍵,查看網頁原始碼),發現原始碼裡並沒有我們需要的數據,而是一個類似於模板的東西。數據是通過後續動態的加載進來的。
當我們用爬蟲去爬的時候,獲取到的也是這樣的原始碼,當然取不到數據啦。
Tips1: 分析網頁的時候,可以先查看一下網頁原始碼,看看自己需要的數據是否在裡面,如果有,則可以繼續接著分析,如果沒有,說明數據是動態加載進來的,要換個思路。
1.2 數據是怎麼獲取到的既然網頁原始碼中找不到數據,那麼我們去哪兒獲得數據呢?
這就涉及到一個詞,叫 「抓包」 ,可能聽上去很高深很難的樣子,其實很簡單的。我們知道數據肯定是通過發起 網絡請求 獲得的,就是網頁向伺服器發送一條請求,然後伺服器把需要的數據回復回來,我們把網頁向伺服器發送的請求,和瀏覽器返回的數據,使用一些工具和手段截獲下來進行分析,這個過程就是 「抓包」。
可能大家聽著還是有點迷糊,下面我來具體演示一下。
首先打開開發者工具,切換到 Network,然後刷新網頁(這裡可以抓到網頁加載過程中,向伺服器發起的各種類型的請求)。
然後上圖紅框中圈出來的,就是我們抓取到的一條一條的請求包,有 js 腳本的,有 css 文件的,還有圖片的等等各種類型的。我們要在這麼多的 「請求包」 裡找到包含我們需要的數據的包。
把列表裡的這些請求從上到下一條一條的點開(在 Preview 裡可以預覽請求返回的數據),查看哪條請求是我們想要找的。
如圖的請求點開以後, Response 裡的內容正好就是版面導航欄裡的內容,我們成功找到了正確的請求。
至此,我們抓包成功!這條請求也就是我們抓到的 "包" 。
1.3 抓到的包怎麼用?含有數據的請求包我們是抓到了,但是我們具體要怎麼用呢?怎麼把它用到爬蟲程序裡,通過它來爬數據呢?
還是那條請求,我們切換到 Headers 頁籤,可以查看到關於這條請求的一些基本信息。
主要關注幾個部分 Request URL(請求連結),Request Method(請求方法),Query String Parameters(請求參數),(當然請求頭的那些東西,User-Agent ,Cookie 什麼的,按照實際情況該怎麼加就怎麼加)。
我們的目的就是,通過 python 代碼模擬瀏覽器發出這條請求,直接獲取伺服器返回的數據(返回的數據就是前面預覽裡的那些)。
import requests
url = "https://www.shobserver.com/staticsg/data/journal/2021-12-22/navi.json?ver=1640171036910"r = requests.get(url)print(r.text)我們簡單寫幾行代碼模擬一下這個過程(url 就是上圖中 Request URL 的內容,requests.get() 是因為 Request Method 是 GET )。
運行代碼,可以成功獲得數據。
1.4 怎麼爬其他日期的數據運行上面的代碼,我們可以獲得到 2021 年 12 月 22日的新聞數據,那我們如果想爬其他日期的新聞數據該怎麼辦呢?
這裡我們觀察一下請求的 url
https://www.shobserver.com/staticsg/data/journal/2021-12-22/navi.json?ver=1640171036910
其中有一段 2021-012-22 的字樣,我們猜測,這個可能就是用來控制獲取數據的日期的,改成別的日期 比如 2021-04-20 再試一下。
https://www.shobserver.com/staticsg/data/journal/2021-04-20/navi.json?ver=1640171036910
發現同樣可以成功。
這樣我們就知道,可以通過修改 url 裡的日期字符串,來爬取指定日期的數據。
1.5 解析數據該請求返回的數據,是 json 格式的字符串,我們需要用 json 庫來進行解析。
有同學可能想問了,那麼一大串亂碼似的文字,你怎麼知道它是 json 格式的呢?簡單來講,看兩個特點,一個是大括號 {} 包起來的,另一個是鍵值對格式,就是 xxx : xxx 這種形式的。實在不知道怎麼判斷的話,就去前面講抓包的部分,看預覽的地方,如果有小箭頭能夠摺疊展開的,就是 json 格式。
我們可以看到 pages 裡有版面的列表,每個版面的 articleList 裡有文章列表,包含了我們需要的版面和文章列表信息。具體解析的 Python 代碼這裡就不講了,文末會貼源碼。
1.6 怎麼爬文章詳細內容首先點開一個文章的正文頁,用前面同樣的分析方法過一遍,很容易知道,正文內容也是動態加載進來的,而且正文的數據是通過下面這條請求來獲得到的。
我們簡單寫段代碼來驗證一下
import requests
url = "https://www.shobserver.com/staticsg/data/journal/2021-12-22/01/article/326701.json?ver=1640174508325"r = requests.get(url)print(r.text)運行結果
經過對這條請求的 url 的分析,我們可以知道,/2021-12-22 是日期,/01 是指版面的編號,/326701 是文章的 id。
https://www.shobserver.com/staticsg/data/journal/2021-12-22/01/article/326701.json?ver=1640174508325
至此,我們完成了對網站的分析,講解了如何判斷網站數據是動態加載還是靜態加載,如果是動態加載的話如何抓包,抓包以後如何使用等等,並抓到了 新聞版面列表,文章列表,文章正文內容 的請求接口。如果有哪裡沒有講清楚,或者對以上內容有不太明白的地方,可以留言問我。
下面進行寫代碼,正式爬取。
二、編碼環節下面是爬蟲源碼,供大家學習交流使用,請勿用於非法用途。
import requestsimport bs4import osimport datetimeimport timeimport json def fetchUrl(url): ''' 功能:訪問 url 的網頁,獲取網頁內容並返回 參數:目標網頁的 url 返回:目標網頁的 html 內容 ''' headers = { 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36', } r = requests.get(url, headers=headers) r.raise_for_status() r.encoding = r.apparent_encoding return r.text
def saveFile(content, path, filename): ''' 功能:將文章內容 content 保存到本地文件中 參數:要保存的內容,路徑,文件名 ''' if not os.path.exists(path): os.makedirs(path) with open(path + filename, 'w', encoding='utf-8') as f: f.write(content)
def download_rmrb(year, month, day, destdir): ''' 功能:網站 某年 某月 某日 的新聞內容,並保存在 指定目錄下 參數:年,月,日,文件保存的根目錄 ''' url = 'https://www.shobserver.com/staticsg/data/journal/' + year + '-' + month + '-' + day + '/navi.json' html = fetchUrl(url) jsonObj = json.loads(html)
for page in jsonObj["pages"]: pageName = page["pname"] pageNo = page["pnumber"] print(pageNo, pageName) for article in page["articleList"]: title = article["title"] subtitle = article["subtitle"] pid = article["id"] url = "https://www.shobserver.com/staticsg/data/journal/" + year + '-' + month + '-' + day + "/" + str(pageNo) + "/article/" + str(pid) + ".json" print(pid, title, subtitle)
html = fetchUrl(url) cont = json.loads(html)["article"]["content"] bsobj = bs4.BeautifulSoup(cont, 'html.parser') content = title + subtitle + bsobj.text path = destdir + '/' + year + month + day + '/' + str(pageNo) + " " + pageName + "/" fileName = year + month + day + '-' + pageNo + '-' + str(pid) + '.txt' saveFile(content, path, fileName) def gen_dates(b_date, days): day = datetime.timedelta(days = 1) for i in range(days): yield b_date + day * i def get_date_list(beginDate, endDate): """ 獲取日期列表 :param start: 開始日期 :param end: 結束日期 :return: 開始日期和結束日期之間的日期列表 """ start = datetime.datetime.strptime(beginDate, "%Y%m%d") end = datetime.datetime.strptime(endDate, "%Y%m%d") data = [] for d in gen_dates(start, (end-start).days): data.append(d) return data
if __name__ == '__main__': ''' 主函數:程序入口,提供了兩種爬取方式,爬取指定日期的新聞 or 爬取時間段內的新聞 ''' beginDate = input('請輸入開始日期:') endDate = input('請輸入結束日期:') data = get_date_list(beginDate, endDate) for d in data: year = str(d.year) month = str(d.month) if d.month >=10 else '0' + str(d.month) day = str(d.day) if d.day >=10 else '0' + str(d.day) download_rmrb(year, month, day, f'Data/{year}') print("爬取完成:" + year + month + day)以上是爬取解放日報新聞文章的爬蟲源碼,如果爬取量較大的話,建議加上 time.sleep() 函數,降低被反爬的機率。
三、運行效果運行程序,輸入 起始日期 和 終止日期 後,爬蟲會自動下載該時間段內的新聞文章,並保存在 Data 目錄下相應的文件夾中。
如果文章中有哪裡沒有講明白,或者講解有誤的地方,歡迎在評論區批評指正,或者掃描下面的二維碼,加我微信,大家一起學習交流,共同進步。