python爬蟲:使用urllib.request和BeautifulSoup抓取新浪新聞標題、鏈接和主要內容


案例一

抓取對象:

新浪國內新聞(http://news.sina.com.cn/china/),該列表中的標題名稱、時間、鏈接。

完整代碼:

 
  1. from bs4 import BeautifulSoup  
  2. import requests  
  3.   
  4. url = 'http://news.sina.com.cn/china/'  
  5. web_data = requests.get(url)  
  6. web_data.encoding = 'utf-8'  
  7. soup = BeautifulSoup(web_data.text,'lxml')  
  8.   
  9. for news in soup.select('.news-item'):  
  10.     if(len(news.select('h2')) > 0):  
  11.         h2 = news.select('h2')[0].text  
  12.         time = news.select('.time')[0].text  
  13.         a = news.select('a')[0]['href']  
  14.         print(h2,time,a)  

運行結果:(只展示部分)

詳細解說:

1. 首先插入需要用到的庫:BeautifulSoup、requests,然后解析網頁。解析完后print下,確認是否解析正確。

 
  1. from bs4 import BeautifulSoup  
  2. import requests  
  3.   
  4. url = 'http://news.sina.com.cn/china/'  
  5. web_data = requests.get(url)  
  6. soup = BeautifulSoup(web_data.text,'lxml')  
  7. print(soup)  

這時,我們可以看到,解析出來的網頁里面有很多亂碼,並沒有正確解析。觀察下結果,看到開頭的這句:

<meta content="text/html; charset=utf-8" http-equiv="Content-type"/>

【charset=utf-8】表示當前內容的字符集是采用utf-8編碼格式,所以我們需要用encoding來解鎖下,這時就能解析出來正常內容。

  1. from bs4 import BeautifulSoup  
  2. import requests  
  3.   
  4. url = 'http://news.sina.com.cn/china/'  
  5. web_data = requests.get(url)  
  6. web_data.encoding = 'utf-8'  
  7. soup = BeautifulSoup(web_data.text,'lxml')  
  8. print(soup)  

2. 解析出網頁后,開始抓取我們需要的內容。首先,先補充幾點知識。

看下面代碼中的第一行,soup.select('.news-item'),取出含有特定CSS屬性的元素時,比如:

  • 找出所有class為news-item的元素,class名前面需要加點(.),即英文狀態下的句號;
  • 找出所有id為artibodyTitle的元素,id名前面需要加井號(#)。

另外,取得含有特定標簽的HTML元素時,直接在select后寫標簽名即可,如下面for循環中的第3行,news.select('h2')。 

  1. for news in soup.select('.news-item'):  
  2.     # print(news)  
  3.     if(len(news.select('h2')) > 0):  
  4.         # print(news.select('h2')[0].text)  
  5.         h2 = news.select('h2')[0].text  
  6.         time = news.select('.time')[0].text  
  7.         a = news.select('a')[0]['href']  
  8.         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

完整代碼:

  1. from bs4 import BeautifulSoup  
  2. import requests  
  3. from datetime import datetime  
  4. import json  
  5. import re  
  6.   
  7. news_url = 'http://news.sina.com.cn/c/nd/2017-05-08/doc-ifyeycfp9368908.shtml'  
  8. web_data = requests.get(news_url)  
  9. web_data.encoding = 'utf-8'  
  10. soup = BeautifulSoup(web_data.text,'lxml')  
  11. title = soup.select('#artibodyTitle')[0].text  
  12. print(title)  
  13.   
  14. time = soup.select('.time-source')[0].contents[0].strip()  
  15. dt = datetime.strptime(time,'%Y年%m月%d日%H:%M')  
  16. print(dt)  
  17.   
  18. source = soup.select('.time-source span span a')[0].text  
  19. print(source)  
  20.   
  21. print('\n'.join([p.text.strip() for p in soup.select('#artibody p')[:-1]]))  
  22.   
  23. editor = soup.select('.article-editor')[0].text.lstrip('責任編輯:')  
  24. print(editor)  
  25.   
  26. 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')  
  27. comments_total = json.loads(comments.text.strip('var data='))  
  28. print(comments_total['result']['count']['total'])  
  29.   
  30. news_id = re.search('doc-i(.+).shtml',news_url)  
  31. 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(正則表達式),然后解析網頁。

先抓取標題:

  1. from bs4 import BeautifulSoup  
  2. import requests  
  3. from datetime import datetime  
  4. import json  
  5. import re  
  6.   
  7. url = 'http://news.sina.com.cn/c/nd/2017-05-08/doc-ifyeycfp9368908.shtml'  
  8. web_data = requests.get(url)  
  9. web_data.encoding = 'utf-8'  
  10. soup = BeautifulSoup(web_data.text,'lxml')  
  11. title = soup.select('#artibodyTitle')[0].text  
  12. print(title)  

datetime、json、re在后面時間轉換和從js中抓取數據時會用到。

我們主要來看下倒數第二行代碼:title = soup.select('#artibodyTitle')[0].text,這里的用法和案例一中一樣,id前用井號(#)來表示該類元素的位置,解鎖唯一元素用[0],提取文本信息text。

2. 抓取時間,並將原有日期格式轉化為標准格式

  1. # time = soup.select('.time-source')[0]  
  2. # print(time)  
  3. time = soup.select('.time-source')[0].contents[0].strip()  
  4. dt = datetime.strptime(time,'%Y年%m月%d日%H:%M')  
  5. 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類的表述方法說明其位置,如下第二行;

  1. # source = soup.select('#navtimeSource > span > span > a')[0].text  
  2. source = soup.select('.time-source span span a')[0].text  
  3. print(source)  

4. 抓取新聞詳情:

  1. article = []  
  2. for p in soup.select('#artibody p')[:-1]:  
  3.     article.append(p.text.strip())  
  4. # print(article)  
  5. print('\n'.join(article))  

第1行:article為空列表;

第2行:通過觀察代碼可以發現,新聞詳情存放在p標簽中,如果直接輸出,能看到最后一欄有責任編輯的信息,如果不想要責任編輯,增加[:-1]可以去除最后一個編輯信息;

第3行:將抓取的數據插入article列表中,.strip()去除空白信息;

第4行:可以先print下看結果是否正確,發現新聞詳情的原標題和正文是在一排的,不是很好,注釋掉后我們換新的方法;

第5行:join()方法用來將列表中的元素用指定的字符連接生成新的字符串,這里用\n換行來連接列表中的原標題和正文兩塊內容。

上面這是一種文章連接的方法,當然還有比較簡潔的寫法。我們將上面的for循環一句拿出來,前面加p.text.strip(),然后整體加中括號,這樣就形成了一個列表,然后前面再用join將其連接起來,就用簡單的一行代碼替代了上面的多行代碼。

  1. print('\n'.join([p.text.strip() for p in soup.select('#artibody p')[:-1]]))  

5. 抓取責任編輯:

這里用到的lstrip是去除左側的內容,括號內‘責任編輯:’是指去除這部分內容,只保留編輯人員姓名。之前用到的strip是去除兩側,lstrip是去除左側,后面會用到的rstrip是去除右側內容。

  1. editor = soup.select('.article-editor')[0].text.lstrip('責任編輯:')  
  2. print(editor)  

6. 抓取評論數:

  1. # comments = soup.select('#commentCount1')  
  2. # print(comment)  
  3. 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')  
  4. # print(comments.text)  
  5. comments_total = json.loads(comments.text.strip('var data='))  
  6. # print(comments_total)  
  7. 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:

  1. 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。

    1. news_id = re.search('doc-i(.+).shtml',news_url)  
    2. # print(news_id.group(0))  
    3. print(news_id.group(1))  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM