案例一
抓取對象:
新浪國內新聞(http://news.sina.com.cn/china/),該列表中的標題名稱、時間、鏈接。
完整代碼:
- from bs4 import BeautifulSoup
- import requests
- url = 'http://news.sina.com.cn/china/'
- web_data = requests.get(url)
- web_data.encoding = 'utf-8'
- soup = BeautifulSoup(web_data.text,'lxml')
- for news in soup.select('.news-item'):
- if(len(news.select('h2')) > 0):
- h2 = news.select('h2')[0].text
- time = news.select('.time')[0].text
- a = news.select('a')[0]['href']
- print(h2,time,a)
運行結果:(只展示部分)
詳細解說:
1. 首先插入需要用到的庫:BeautifulSoup、requests,然后解析網頁。解析完后print下,確認是否解析正確。
- from bs4 import BeautifulSoup
- import requests
- url = 'http://news.sina.com.cn/china/'
- web_data = requests.get(url)
- soup = BeautifulSoup(web_data.text,'lxml')
- print(soup)
這時,我們可以看到,解析出來的網頁里面有很多亂碼,並沒有正確解析。觀察下結果,看到開頭的這句:
<meta content="text/html; charset=utf-8" http-equiv="Content-type"/>
【charset=utf-8】表示當前內容的字符集是采用utf-8編碼格式,所以我們需要用encoding來解鎖下,這時就能解析出來正常內容。
- from bs4 import BeautifulSoup
- import requests
- url = 'http://news.sina.com.cn/china/'
- web_data = requests.get(url)
- web_data.encoding = 'utf-8'
- soup = BeautifulSoup(web_data.text,'lxml')
- print(soup)
2. 解析出網頁后,開始抓取我們需要的內容。首先,先補充幾點知識。
看下面代碼中的第一行,soup.select('.news-item'),取出含有特定CSS屬性的元素時,比如:
- 找出所有class為news-item的元素,class名前面需要加點(.),即英文狀態下的句號;
- 找出所有id為artibodyTitle的元素,id名前面需要加井號(#)。
另外,取得含有特定標簽的HTML元素時,直接在select后寫標簽名即可,如下面for循環中的第3行,news.select('h2')。
- for news in soup.select('.news-item'):
- # print(news)
- if(len(news.select('h2')) > 0):
- # print(news.select('h2')[0].text)
- h2 = news.select('h2')[0].text
- time = news.select('.time')[0].text
- a = news.select('a')[0]['href']
- print(h2,time,a)
現在來詳細看下這段代碼每行的釋義。
第1行:soup.select('.news-item'),取出news-item該類中的元素;
第2行:print下news,查看是否被正常解析,正常后繼續,不用時可以注釋掉;
第3行:通過觀察代碼,可以看到標題被儲存在標簽h2中,如果h2的長度大於0,這里是為了去除為空的標題數據;
第4行:print中news.select('h2')[0].text,[0]是取該列表中的第一個元素,text是取文本數據,print后查看是否正確,不用時可以注釋掉;
第5行:將news.select('h2')[0].text存儲在變量h2中;
第6行:time是class類型,前面加點來表示,同上,將其數據存儲在變量time中;
第7行:我們要抓取的鏈接存放在a標簽中,鏈接已經不是text了,后面用href,將鏈接數據存儲在變量a中;
第8行:最后輸出我們想要抓取的數據,標題、時間、鏈接。
案例二
抓取對象:
抓取新聞詳情頁的標題、時間(進行格式轉換)、新聞來源、新聞詳情、責任編輯、評論數量和新聞ID。
示例新聞:http://news.sina.com.cn/c/nd/2017-05-08/doc-ifyeycfp9368908.shtml
完整代碼:
- from bs4 import BeautifulSoup
- import requests
- from datetime import datetime
- import json
- import re
- news_url = 'http://news.sina.com.cn/c/nd/2017-05-08/doc-ifyeycfp9368908.shtml'
- web_data = requests.get(news_url)
- web_data.encoding = 'utf-8'
- soup = BeautifulSoup(web_data.text,'lxml')
- title = soup.select('#artibodyTitle')[0].text
- print(title)
- time = soup.select('.time-source')[0].contents[0].strip()
- dt = datetime.strptime(time,'%Y年%m月%d日%H:%M')
- print(dt)
- source = soup.select('.time-source span span a')[0].text
- print(source)
- print('\n'.join([p.text.strip() for p in soup.select('#artibody p')[:-1]]))
- editor = soup.select('.article-editor')[0].text.lstrip('責任編輯:')
- print(editor)
- comments = requests.get('http://comment5.news.sina.com.cn/page/info?version=1&format=js&channel=gn&newsid=comos-fyeycfp9368908&group=&compress=0&ie=utf-8&oe=utf-8&page=1&page_size=20')
- comments_total = json.loads(comments.text.strip('var data='))
- print(comments_total['result']['count']['total'])
- news_id = re.search('doc-i(.+).shtml',news_url)
- print(news_id.group(1))
運行結果:
國土部:5月到9月實行汛期地質災害日報告制度
2017-05-08 17:21:00
央視新聞
原標題:國土資源部:地質災害高發期 實行日報告制度
國土資源部消息,5月份將逐漸進入地質災害的高發期,防災減災形勢更加嚴峻。據中國氣象局預計,5月份我國江南大部、華南東部、西北地區大部降水較常年同期偏多,應加強防范極端氣象事件誘發的滑坡、泥石流等地質災害。對此從5月起至9月,國土資源部應急辦實行汛期地質災害日報告制度,各地必須將每天發生的災情險情及其重大工作部署於當天下午3點前報告國土資源部應急辦。
李偉山
4
fyeycfp9368908
詳細解說:
1. 首先插入需要用到的庫:BeautifulSoup、requests、datetime(時間處理)、json(解碼:把json格式字符串解碼轉換成Python對象)、re(正則表達式),然后解析網頁。
先抓取標題:
- from bs4 import BeautifulSoup
- import requests
- from datetime import datetime
- import json
- import re
- url = 'http://news.sina.com.cn/c/nd/2017-05-08/doc-ifyeycfp9368908.shtml'
- web_data = requests.get(url)
- web_data.encoding = 'utf-8'
- soup = BeautifulSoup(web_data.text,'lxml')
- title = soup.select('#artibodyTitle')[0].text
- print(title)
datetime、json、re在后面時間轉換和從js中抓取數據時會用到。
我們主要來看下倒數第二行代碼:title = soup.select('#artibodyTitle')[0].text,這里的用法和案例一中一樣,id前用井號(#)來表示該類元素的位置,解鎖唯一元素用[0],提取文本信息text。
2. 抓取時間,並將原有日期格式轉化為標准格式
- # time = soup.select('.time-source')[0]
- # print(time)
- time = soup.select('.time-source')[0].contents[0].strip()
- dt = datetime.strptime(time,'%Y年%m月%d日%H:%M')
- print(dt)
第1行:抓取時間;
第2行:print下時間,這時會發現運行結果中既含有時間,還有新聞的來源,如下:
<span class="time-source" id="navtimeSource">2017年05月08日17:21<span>
<span data-sudaclick="media_name"><a href="http://m.news.cctv.com/2017/05/08/ARTIPEcvpHjWzuGDPWQhn77z170508.shtml" rel="nofollow" target="_blank">央視新聞</a></span></span>
</span>
那么,接下來我們需要想辦法將時間和來源分開來,這時需要使用到contents;
第3行:我們先在后面加上.contents,運行下后會看到上面的內容會在列表中分為如下2個元素,此時,我們取的時間為第一個元素,在contents后加[0],最后的.strip()可以去除時間末尾的\t;
['2017年05月08日17:21\t\t', <span>
<span data-sudaclick="media_name"><a href="http://m.news.cctv.com/2017/05/08/ARTIPEcvpHjWzuGDPWQhn77z170508.shtml" rel="nofollow" target="_blank">央視新聞</a></span></span>, '\n']
第4行:用datetime.strptime()來做時間格式化,原有的時間是年月日時分,所以轉化時用到年月日時分,其中%Y是四位數的年份表示、%m是月份、%d是月份內的一天、%H是24小時制中的小時、%M是分鍾數,並將其存儲在變量dt中;
第5行:輸出dt,會得到格式化后的時間,比如:2017-05-08 17:21:00。
3. 抓取新聞來源:
之前文章《Python爬蟲:爬取人人都是產品經理的數據 》中有提到可以用【Copy selector】來復制粘貼出新聞來源的位置,如下第一行;也可以用本篇文章經常用到的class類的表述方法說明其位置,如下第二行;
- # source = soup.select('#navtimeSource > span > span > a')[0].text
- source = soup.select('.time-source span span a')[0].text
- print(source)
4. 抓取新聞詳情:
- article = []
- for p in soup.select('#artibody p')[:-1]:
- article.append(p.text.strip())
- # print(article)
- print('\n'.join(article))
第1行:article為空列表;
第2行:通過觀察代碼可以發現,新聞詳情存放在p標簽中,如果直接輸出,能看到最后一欄有責任編輯的信息,如果不想要責任編輯,增加[:-1]可以去除最后一個編輯信息;
第3行:將抓取的數據插入article列表中,.strip()去除空白信息;
第4行:可以先print下看結果是否正確,發現新聞詳情的原標題和正文是在一排的,不是很好,注釋掉后我們換新的方法;
第5行:join()方法用來將列表中的元素用指定的字符連接生成新的字符串,這里用\n換行來連接列表中的原標題和正文兩塊內容。
上面這是一種文章連接的方法,當然還有比較簡潔的寫法。我們將上面的for循環一句拿出來,前面加p.text.strip(),然后整體加中括號,這樣就形成了一個列表,然后前面再用join將其連接起來,就用簡單的一行代碼替代了上面的多行代碼。
- print('\n'.join([p.text.strip() for p in soup.select('#artibody p')[:-1]]))
5. 抓取責任編輯:
這里用到的lstrip是去除左側的內容,括號內‘責任編輯:’是指去除這部分內容,只保留編輯人員姓名。之前用到的strip是去除兩側,lstrip是去除左側,后面會用到的rstrip是去除右側內容。
- editor = soup.select('.article-editor')[0].text.lstrip('責任編輯:')
- print(editor)
6. 抓取評論數:
- # comments = soup.select('#commentCount1')
- # print(comment)
- comments = requests.get('http://comment5.news.sina.com.cn/page/info?version=1&format=js&channel=gn&newsid=comos-fyeycfp9368908&group=&compress=0&ie=utf-8&oe=utf-8&page=1&page_size=20')
- # print(comments.text)
- comments_total = json.loads(comments.text.strip('var data='))
- # print(comments_total)
- print(comments_total['result']['count']['total'])
首先,我們先select('#commentCount1')來篩選出評論數,這時print下輸出結果為[<span id="commentCount1"></span>],span中間為空白信息,沒有我們想要的評論數。
這時,我們需要重新去觀察下網頁代碼,發現評論數可能是通過JavaScript來增加上去的,那么我們需要找到是從哪里調用JavaScript(即JS)的。
鼠標放在評論數(4)上,Google瀏覽器鼠標右鍵點擊“檢查”,選擇頂部的network,然后在下面海量的鏈接里找到有數量4的鏈接。
復制該鏈接,中間加粗部分是newid,最后有一段像時間戳的字串“&jsvar=loader_1494295822737_91802706”,我們需要把這塊內容去掉,然后試着print下,可以發現結果也不會有影響。
http://comment5.news.sina.com.cn/page/info?version=1&format=js&channel=gn&newsid=comos-fyeycfp9368908&group=&compress=0&ie=utf-8&oe=utf-8&page=1&page_size=20&jsvar=loader_1494295822737_91802706
處理好鏈接后,那么,我們該如何從JavaScript中讀取數據呢?這時我們需要使用到json來處理,在開頭,我們導入庫的時候已經import json了,這里可以直接使用。
json.loads是用於解碼json數據的。第5行,我們將解碼后的數據存儲在變量comments_total中;第6行,我們print該變量時,會得到結果,發現除了評論數之外,還有一些其他的信息。
所以我們需要重新輸出,根據上一行print的結果,觀察發現,最后一行我們可以用['result']['count']['total']來表示評論數的位置。
7. 抓取新聞ID:
- print(news_url.split('/')[-1].rstrip('.shtml').lstrip('doc-i'))
剛在上面第6點抓取評論數時,我們發現鏈接中有一個newsid,該新聞頁面的鏈接中也有相同的ID部分,這時我們可以明確其新聞ID的位置。
split()是通過指定分隔符對字符串進行切片,[-1]是截取最后一個元素,然后用rstrip去除掉末尾的.shtml,用lstrip去除掉左側的doc-i,得到我們想要的新聞ID。
除了這種方法外,我們還可以用正則表達式來表達。這時我們需要用到re庫,在開頭時,我們已經事先import re了,這里直接使用。
其中re.search是掃描整個字符串並返回第一個成功的匹配,group()是匹配到的字符串,其中group(0),是匹配到的字符串全部顯示(doc-ifyeycfp9368908.shtml),group(1)是(.+)內的顯示,即我們想要的新聞ID。
- news_id = re.search('doc-i(.+).shtml',news_url)
- # print(news_id.group(0))
- print(news_id.group(1))