新聞的搜集與資料庫的建立
- 寫在前面
-
- 1. 爬取什麼
- 2. 怎麼爬
-
- 2.1 分析網頁的HTML源碼,找到規律
-
- 2.1.1 分析滾動頁面
- 2.1.2 分析新聞頁面
- 2.2 分析完畢,開始動手寫代碼爬網頁
-
- 2.2.1 爬滾動頁面
- 2.2.2 爬新聞頁面
- 3. 爬完幹什麼
-
-
- 3.1 分析新聞内容,提取出有關搜尋的關鍵資訊
- 3.2 儲存入資料庫
-
- 4. 重新整理一下,馬上回來
- 寫在後面
寫在前面
大家好,這一章主要來介紹如何選取爬取的頁面,如何确定爬取内容的方法和怎樣建立資料庫。
如需檢視完整源代碼,請移步我的github頁面,本文所講内容在
Refresh.py
檔案裡。
其他章節請通路我的這篇部落格。
1. 爬取什麼
我們選取頁面的原則是頁面要盡可能地“整齊”。什麼是“整齊”呢,請看下面兩圖:
其中圖一是新浪新聞的首頁,圖二是新浪新聞的滾動頁面。顯然從視覺直覺來說,圖二比圖一更為地整齊。
為什麼我們要選擇比較整齊地頁面進行爬取呢?原因在于這種頁面的HTML代碼也比較地有規律,爬網頁的時候也更為地省心。比如圖二的每一則新聞的代碼都是這樣的:
是以我們隻要在爬取的過程中,找到每個c_tit類下的href就可以找到每條新聞對應的網頁位址了。
2. 怎麼爬
(大家可以參考網易雲上的免費視訊課程,當時我就是就着這個視訊學的,雖然視訊中的爬取方法因為新浪新聞網頁架構的更改失效了,但可以從中學到網絡爬蟲的基本方法)
2.1 分析網頁的HTML源碼,找到規律
2.1.1 分析滾動頁面
我們使用google的chrome浏覽器進行分析。在chrome浏覽器中搜尋“新浪新聞”,點選進入新浪新聞首頁。也就是這樣:
然後點選左上角的“滾動”(圖中用紅圈标出的地方),進入滾動頁面。
之後我們進入開發者模式。如下圖所示,點選“開發者工具”:
然後按下圖的步驟點選:
(由于上一條新聞《27歲未婚女孩…》太過驚悚,我們選一個溫和點的)
然後就出現了下圖:
圖中被選中的代碼就是我們想從這個頁面上提取的資訊(即每條新聞的網址)。
隻要知道每條新聞的網址,然後我們再通路每條新聞的頁面,提取出關于該新聞的資訊即可。
是以接下來我們進入這條新聞的頁面,找出新聞頁面的HTML代碼規律。
2.1.2 分析新聞頁面
我們要找的資訊:新聞的标題、日期、正文和關鍵字。
按照之前的套路,我們打開開發者工具,并找到正文的位置:
标題、日期以及關鍵字的尋找方法請看下文。
明确了這些,接下來我們就可以開始動手爬新聞了。?
2.2 分析完畢,開始動手寫代碼爬網頁
2.2.1 爬滾動頁面
經過一番尋找與嘗試(具體嘗試的步驟請在上文所列出的視訊教程中檢視,說實話,當初嘗試了挺久才碰巧找到的),我發現在Source中有一個feed啥的比較關鍵:
請注意紅下劃線的地方。我們發現位址可以拼接成:
https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d
的形式。其中%d可以代入數字,表示第幾頁的滾動頁面。
後面的
r=0.02768994656458923&callback=jQuery1112035449799366314805_1550924012864&_=1550924012872
與頁面的實時重新整理有關,我們不去管他。
然後我們編寫代碼去通路這個位址:(其中maxPage是我設定的滾動頁面的最大頁碼)
for page in range(1,maxPage+1):
url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d'%(page)
try:
res = requests.get(url)
except:
continue
注意try – except,因為可能有些網頁是無法通路的,為了提高容錯性,我們加一個try–except,如果通路錯誤則忽略,直接通路下一頁滾動頁面。
如果成功通路,我們得到的res就是一個response對象,然後對這個response對象進行處理。
把它丢進BeautifulSoup中進行解析:(BeautifulSoup是一個從HTML或XML檔案中提取出資料的Python庫,簡單來說,他可以将檔案中的标簽整理成樹狀結構,友善查閱和應用。不了解的讀者請自行百度或google)
for page in range(1,maxPage+1):
url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d'%(page)
#url = 'https://news.sina.com.cn/roll/#pageid=153&lid=2509&k=&num=50&page=%d'%(page)
try:
res = requests.get(url)
except:
continue
pageInfo = []
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text, 'html.parser')
jd = json.loads(soup.text)
我們看jd是一個什麼東東:
原來jd是一個字典,
jd['result']['data']
中的每個元素都是滾動頁面中的一則新聞,裡面中有很多我們要找的資訊:url, title, keywords等。
for page in range(1,maxPage+1):
url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d'%(page)
try:
res = requests.get(url)
except:
continue
pageInfo = []
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text, 'html.parser')
jd = json.loads(soup.text)
for info in jd['result']['data']:
url = info['url']
title = info['title']
keywords = info['keywords']
2.2.2 爬新聞頁面
結束了嗎?好像還少了點啥…對,少了時間和最重要的正文。這就要我們從每則新聞的新聞頁面中爬取。
在2.2.1中我們已經得到了所有新聞的網頁位址(url),我們進入這個編寫一個名為
getUrlInfo
的函數,提取網頁中的各類資訊:
def getUrlInfo(url):
try:
res = requests.get(url)
res.encoding = 'utf-8'
#print(res.text)
soup = BeautifulSoup(res.text, 'html.parser')
article = soup.select('.article p')[:-1]
article = '\n\t'.join([p.text.strip() for p in article]) #将文章中的每一段用\n\t隔開
date = soup.select('.date')[0].text
date = datetime.strptime(date, '%Y年%m月%d日 %H:%M')
result = {}
result['date'] = date
result['article'] = article
except:
return #如果出錯,則傳回None
return result
然後将date, article 等資訊與上文的url, title, keywords合并,并将每則新聞的這些資訊提取出來儲存在
news
字典中,再将每頁滾動頁面中所有新聞對應的字典插入到
pageInfo
的數組中,最後将pageInfo中的資訊插入到
allPages
數組中:
def dealPages(maxPage):
global allPages
allPages = []
for page in range(1,maxPage+1):
url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d'%(page)
try:
res = requests.get(url)
except:
continue
pageInfo = []
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text, 'html.parser')
jd = json.loads(soup.text)
for info in jd['result']['data']:
url = info['url']
title = info['title']
keywords = info['keywords']
date_article = getUrlInfo(url)
if date_article == None:
continue
date = date_article['date']
date = date.strftime('%Y-%m-%d %H:%M')
article = date_article['article']
news = {}
news['url'] = url
news['title'] = title
news['keywords'] = keywords
news['date'] = date
news['article'] = article
pageInfo.append(news)
allPages.extend(pageInfo)
即
news --- 每條新聞的資訊
pageInfo --- 每頁滾動頁面的資訊
allPages --- 所有滾動頁面的資訊
這樣,dealPages和getUrlInfo兩個函數雙劍合璧,頁碼為1-maxPage的滾動頁面的資訊就被我提取出來了。
3. 爬完幹什麼
3.1 分析新聞内容,提取出有關搜尋的關鍵資訊
首先我們要去掉新聞中與搜尋無關的詞。這裡就要引入“停用詞”的概念。
比如在一句話中:“小明和小紅去北京天安門玩了”,“小紅”、“小明”、“北京”、“天安門”都是對于搜尋有用的資訊,而“和”、“了”這些詞就和搜尋無關了。這些與搜尋無關的詞就叫“停用詞”。網上有停用詞的彙總,本文用的是這一個停用詞表。
為了提高搜尋速度,我們隻針對标題和關鍵詞進行搜尋,而關鍵詞中是沒有停用詞的,是以隻需去掉标題中的停用詞。
首先我們用
jieba
對标題進行分詞,然後去除停用詞以及标點。将剩下的詞連同關鍵詞一起作為搜尋的依據。統計每個詞出現的頻率,計入名為
Terms
的字典中,再将每則新聞
Terms
字典彙總入
Termdict
的list中:
def build_TermDict():
global TermDict, N, avg_l
global allPages
TermDict.clear()
avg_l = 0 #文檔平均長度,即平均每個新聞中有多少個有效詞
N = len(allPages)
cloudTerm = [] #用于雲圖建構
for doc_id in range(0,N):
doc = allPages[doc_id]
#toCut = doc['title']*30 + doc['keywords']*15+doc['article'] #設定不同的權重
toCut = doc['title']+doc['keywords']
terms = jieba.cut_for_search(toCut)
ld = 0 #文檔長度,即文章中有效詞的個數
#去除标點和停用詞
Terms = {}
for p in terms:
p = p.strip()
if len(p)>0 and p not in stop_words and not p.isdigit():
if p in Terms:
Terms[p] += 1
else:
Terms[p] = 1
cloudTerm.append(p)
ld += 1
avg_l += 1
#将Terms中元素加入TermDict
for p in Terms:
if p in TermDict:
TermDict[p][0] += Terms[p]
else:
TermDict[p] = [1,[]]
TermDict[p][1].append('%d\t%s\t%d\t%d'%(doc_id, doc['date'], Terms[p],ld ))
avg_l /= N
#生成雲圖
cloudText = ','.join(cloudTerm)
wc = WordCloud(
background_color="white", #背景顔色
max_words=200, #顯示最大詞數
font_path="simhei.ttf", #使用字型
min_font_size=15,
max_font_size=50,
width=400 #圖幅寬度
)
wc.generate(cloudText)
wc.to_file("wordcloud.gif")
#将TermDict中文檔資訊合并成一個字元串,用\n隔開
for i in TermDict:
TermDict[i][1] = '\n'.join(TermDict[i][1])
ok✌️
3.2 儲存入資料庫
資訊提取完…當然是把資料存到資料庫中啊?
資料庫的好處在于:每次搜尋時不需要重新提取網頁中的資訊,直接在資料庫中查找就ok了。想象一下,如果每次搜尋都要等待幾分鐘讓程式爬取網頁的資訊,這酸爽,不敢相信啊。
在這裡我們用sqlite3存儲資料,代碼如下(如不熟悉sqlite3請自行百度,網上很多講解的?):
def WriteInDataBase():
#将TermDict寫入資料庫
global TermDict
df = pandas.DataFrame(TermDict).T
db = sqlite3.connect('news.sqlite')
df.to_sql('TermDict',con = db,if_exists='replace') #如果表存在就将表替代
db.close()
#将N 和 avg_l寫入txt檔案中
paraF = open('parameter.txt','w')
paraF.write('%d\t%d'%(N,avg_l))
paraF.close()
#将allPages寫入資料庫
df = pandas.DataFrame(allPages)
db = sqlite3.connect('news.sqlite')
df.T.to_sql('allPages',con = db,if_exists='replace') #如果表存在就将表替代
db.close()
4. 重新整理一下,馬上回來
到此為止,搜尋新聞、處理新聞資訊以及建立資料庫的工作就完成了,我們最後用一個
refresh
函數來将上面寫的函數串在一起,完成重新整理的工作:
def refresh():
global maxPage
global allPages, TermDict, stop_words, N, avg_l
#處理滾動頁面
allPages = []
dealPages(maxPage)
#建構清單stop_words,包含停用詞和标點
stop_words = []
build_StopWords()
#建構TermDict
TermDict={}
N = 0
avg_l = 0
build_TermDict()
#将TermDict轉化為pandas中的DataFrame,再寫入資料庫sqlite
WriteInDataBase()
小小地測試一下,嗯,不錯:
if __name__ == '__main__':
refresh()
這樣,這一章的任務——新聞的搜集與資料庫的建立,就完成了?
寫在後面
如需檢視完整源代碼,請移步我的github頁面,本文所講内容在
Refresh.py
檔案裡。
如有不當,歡迎大神指出!