用網路爬蟲爬取新浪新聞----Python網路爬蟲實戰學習筆記
今天學完了網易雲課堂上Python網路爬蟲實戰的全部課程,特在此記錄一下學習的過程中遇到的問題和學習收穫。
我們要爬取的網站是新浪新聞的國內版首頁
下面依次編寫各個功能模組
1.得到某新聞頁面下的評論數
評論數的資料是個動態內容,應該是存在伺服器上,由js指令碼傳過來的,因此我們f12開啟開發者工具在network下面找到js一欄,觀察各個請求的preview頁面,看看評論數包含在哪個請求中
經過一番查詢後,可以發現在這個請求中含有評論數目的資料,因此可以通過爬取這個請求獲取評論數
這是老師給出的爬取評論數的函式
import re
import requests
import json
commentURL='http://comment5.news.sina.com.cn/page/info?version=1&format=json&channel=gn&newsid=comos-{}&group=undefined&\
compress=0&ie=utf-8&oe=utf-8&page=1&page_size=3&t_size=3&h_size=3&thread=1'
def getCommentCounts(newsurl):
m = re.search('doc-i(.*).shtml' ,newsurl)
newsid = m.group(1)
comments = requests.get(commentURL.format(newsid))
jd = json.loads(comments.text)
return (jd['result']['count']['total'])
由於新聞頁面的評論的連結應該都比較相似,用於區分不同新聞的只是用到了newsid一項資料,因此可以先用{}代替,在批量抓取時用newsid替換即可。但是在實際使用時發現有的新聞評論抓取不成功,在用連結替換了newsid之後在位址列開啟會返回一個含有exception的錯誤資料,提示我們向新浪傳送的這個請求出現異常,並沒有找到這個新聞的評論,這是怎麼回事呢?開啟這條新聞,我發現他的評論的請求URL與其他的新聞在channel部分有些不同,大部分的都是gn,而少數則是sh,而我們沒有考慮到這種情況,這樣在遇到channel為sh的時候就無法抓取到評論,該如何解決呢?我最先想到是用Python的try except來處理沒有找到頁面的異常,但是可能由於異常是在伺服器端的,伺服器返回的資訊只是告訴我地址有異常,而不是程式出現異常,並不能捕獲到,因此我採用了一個比較低階但是很有效的方法:檢測伺服器返回的資訊中是否含有exception欄位,下面是考慮到這點後改進的函式,同時加入了處理超時異常的部分
import re
import requests
import json
commentURL1 = 'http://comment5.news.sina.com.cn/page/info?version=1&format=json&channel=gn&newsid=comos-{}&group=undefined&compress=0&ie=utf-8&oe=utf-8&page=1&page_size=3'
commentURL2 = 'http://comment5.news.sina.com.cn/page/info?version=1&format=json&channel=sh&newsid=comos-{}&group=undefined&compress=0&ie=utf-8&oe=utf-8&page=1&page_size=3'
def getCommentCount(newsurl): #得到某個新聞下面的評論數 #也有可能超時 也有可能是gn和sh
try:
m = re.search('doc-i(.*).shtml',newsurl)
newsid = m.group(1)
comments = requests.get(commentURL1.format(newsid),timeout=1)
jd = json.loads(comments.text)
flag = 'exception'
if (flag in comments.text):
comments = requests.get(commentURL2.format(newsid),timeout=1)
jd = json.loads(comments.text)
return jd['result']['count']['total']
else:
return jd['result']['count']['total']
except Exception as e:
return "timeoutflag"
2.得到某個新聞的全部有效資訊
我們想要得到的有標題、編輯、來源、時間、評論數、內容共六項資訊,下面在網頁中看看這些資訊都被放在了什麼地方
標題
編輯
來源和時間
內容
評論數我們使用getCommentCount方法獲得
將這些獲得資訊的程式碼封裝為函式(注意新聞頁面部分元素名稱做了修改)
import requests
from bs4 import BeautifulSoup
def getNewsDetail(newsurl):
result = {}
res = requests.get(newsurl) #bug
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text,'html.parser')
result['title'] = soup.select('.main-title')[0].text
result['newssource'] = soup.select('.date-source a')[0].text
result['time'] = soup.select('.date')[0].text
result['article'] = ' '.join([p.text.strip() for p in soup.select('#article p')[:-2]])
result['editor'] = soup.select('.show_author')[0].text.strip("責任編輯:")
result['comments'] = getCommentCounts(newsurl)
return result
程式碼使用的時候,在採集新聞來源上出現和設計模組一時同樣的問題:有些來源採集不到,對採集不到的新聞頁面進行觀察發現這些新聞的來源部分元素名稱不太一樣,因此在採集時需要做個判斷,同時也加入了對採集的超時處理,下面是改進後的程式碼
from bs4 import BeautifulSoup
def getNewsDetail(newsurl): #得到某個新聞的全部資訊
result = {}
try:
res = requests.get(newsurl,timeout=0.5)
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text,'html.parser')
result['title'] = soup.select('.main-title')[0].text
if len(soup.select('.date-source a')):
result['newssource'] = soup.select('.date-source a')[0].text
else:
result['newssource'] = soup.select('.date-source .source')[0].text
result['time'] = soup.select('.date')[0].text
result['article'] = ' '.join([p.text.strip() for p in soup.select('#article p')[:-2]])
result['editor'] = soup.select('.show_author')[0].text.strip("責任編輯:")
result['comments'] = getCommentCount(newsurl)
return result
except Exception as e:
return result
3.得到某頁全部新聞的資訊
在點選下一頁按鈕的時候觀察js請求,會發現新增了三個
其中一個就記錄了該頁的新聞的標題和連結,我們可以根據這個請求找到這一頁所有新聞的連結,下面是實現這個功能的函式
def parseListLinks(url): #得到某頁新聞的資訊
newsdetails = []
res = requests.get(url)
jd = json.loads(res.text.lstrip(' newsloadercallback(').rstrip(');'))
for ent in jd['result']['data']:
newsdetails.append(getNewsDetail(ent['url']))
return newsdetails
但是我們使用這個函式必須知道載入一頁新聞的請求URL,開啟這個請求的Headers,把他的URL複製下來
把page的賦值部分用{}代替,接下來我們使用這個函式對新浪新聞前三頁的新聞資訊進行爬取
4.呼叫函式進行新聞爬取
url = 'http://api.roll.news.sina.com.cn/zt_list?channel=news&cat_1=gnxw&\
cat_2==gdxw1||=gatxw||=zs-pl||=mtjj&level==1||=2&show_ext=1&show_all=1&show_num=22&\
tag=1&format=json&page={}'
news_total = []
for i in range(1,4):
newsurl = url.format(i)
newsary = parseListLinks(newsurl)
news_total.extend(newsary)
顯示爬到的資訊
import pandas
df = pandas.DataFrame(news_total)
pandas.set_option('display.max_rows',None)
df
存入資料庫,我的資料庫選擇的是mysql,沒有用老師的sqlite
from sqlalchemy import create_engine
import pymysql
import pandas as pd
engine = create_engine("mysql+pymysql://root:[email protected]:3306/sina?charset=utf8")
df.to_sql(name = 'news',con = engine,if_exists = 'append',index = False,index_label = False)
顯示資料庫資訊
conn = pymysql.connect(host="localhost",user="root",passwd="wasd1234",db='sina',port = 3306,charset="utf8")
sql = "select * from news "
df = pd.read_sql(sql,conn)
pandas.set_option('display.max_rows',None)
df