智能決策上手系列教程索引
以前在簡書發(fā)了一些文章节预,涉及的分類特別雜亂羊赵,有TensorFlow的漏麦,有Web開發(fā)的骗灶,還有一些小學生編程教程和繪圖設計教程...最近又在做人工智能通識專題和智能決策系列教程的文章游添。
這些天很多簡友關注我系草,但我很迷茫通熄,并不知道哪些文章最受大家重視,對大家更有用些找都,而簡書也沒有這方面的統(tǒng)計功能開放給作者們使用唇辨。
我就想能不能自己把這些變化數(shù)據(jù)抓取下來,自己分析一下能耻,于是就開動寫這個案例教程了赏枚。
這個教程推薦使用Chrome瀏覽器和Jupyter Notebook編輯器。Notebook的安裝請參照安裝Anaconda:包含Python編程工具Jupyter Notebook
有哪些數(shù)據(jù)可以獲认汀嗡贺?
從自己的文章列表頁面可以看到總體【關注數(shù)】和每篇文章的【觀看數(shù)】都是直接獲取的,我們只要匯總每天哪些文章觀看數(shù)增加了鞍帝,再對比粉絲數(shù)的變化诫睬,就能知道哪些文章引發(fā)的關注最多。
因為目測我的文章每天總閱讀量的增加數(shù)帕涌,和每天粉絲的增加數(shù)相差不太大摄凡,也就是說,大部分閱讀都引發(fā)了被關注蚓曼,所以兩者之間是強關聯(lián)的亲澡。
如果不是這樣,比如每天增加閱讀1萬纫版,粉絲增加100床绪,那就不好說了,因為可能A文章被觀看100次都引發(fā)了被關注其弊,而B文章被觀看了9000次卻沒有引發(fā)一個被關注癞己,那么就沒辦法從單個文章閱讀量上分析粉絲變化,也就猜不出哪些文章更受喜歡梭伐。
爬蟲數(shù)據(jù)是在html里還是在動態(tài)json請求里痹雅?
首先我們要知道頁面上這些數(shù)據(jù)是怎么來的,是直接html標簽顯示的糊识?還是通過JavaScript動態(tài)填充的绩社?請參閱系列教程的前4節(jié)。
我們的套路:
右鍵【顯示網(wǎng)頁源代碼】赂苗,打開的就是瀏覽器地址欄里面的地址請求直接從服務器拉取到的html數(shù)據(jù)愉耙,如果這里可以Ctrl+F搜索到需要的數(shù)據(jù)(比如可以搜到“人工智能通識-AI發(fā)展簡史-講義全篇”),那么用最簡單的html數(shù)據(jù)提取就可以拌滋。
如果上面一個辦法搜不到朴沿,那就右鍵【檢查元素】,然后查看Network面板里面type為xhr的請求鸠真,點擊每一個悯仙,看哪個Response里面可以Ctrl+F搜到我們需要的數(shù)據(jù)。(很多時候可以從請求的英文名字里面猜個八九不離十)
在這個案例里吠卷,我們需要的數(shù)據(jù)看上去就在網(wǎng)頁源代碼里面锡垄,暫且是這樣。
怎么用header和params模擬瀏覽器祭隔?
為了不讓網(wǎng)站的服務器知道我們是爬蟲货岭,就還要像瀏覽器一樣發(fā)送附加的額外信息,就是header和params疾渴。
我們右鍵【檢查】千贯,然后切換到【Network】網(wǎng)絡面板,然后刷新網(wǎng)頁搞坝,我們會看到一個和網(wǎng)頁地址一致的請求搔谴。
如下圖,我的主頁地址是http://www.reibang.com/u/ae784c57b353桩撮,就看到Network最頂上的是ae784c57b353:
如果我們切換到請求的Response響應結果面板敦第,就可以看到這個請求獲取的實際就是網(wǎng)頁源代碼。它的type類型是document店量,也就是html文檔芜果。
就是它了,我們需要它的header頭信息和params參數(shù)融师。
【右擊-Copy-Copy Request Headers】就能復制到這個請求的頭信息了右钾。
但是,如果你留意旱爆,就會發(fā)現(xiàn)這個請求的Response結果(也就是網(wǎng)頁源代碼)并不是包含所有文章舀射,而只是只包含了9個文章。但是如果我們用鼠標往下滾動頁面怀伦,發(fā)現(xiàn)文章就會越來越多的自動添加進來后控。(右側的滾動條越變越短)
我們刷新頁面重置,然后清空Network網(wǎng)絡面板空镜,一點點輕輕往下滾動浩淘,直到列表里出現(xiàn)了一個xhr請求:
點擊這個請求,可以在Headers里看到它的Parameters參數(shù)吴攒,其實就是請求名稱的問號后面的部分:
order_by
是排序张抄,page
是第幾頁。所以不是簡書文章列表不分頁洼怔,默認加載的是page=1
署惯,而當你往下滾動的時候自動添加下一頁的內容page=2,page=3...
。
我們查看它的Response也會發(fā)現(xiàn)镣隶,它所得到的內容和我們上面的網(wǎng)頁源代碼格式是一致的:
設定參數(shù)
打開Jupyter Notebook极谊,在第一個cell單元編寫代碼诡右,設定相關參數(shù)(headers字段涉及到個人隱私已經被我簡化了,你須要在瀏覽器里面復制自己的):
url = 'http://www.reibang.com/u/ae784c57b353'
params = {'order_by': 'shared_at', 'page': '1'}
headers = '''
GET /u/ae784c57b353 HTTP/1.1
Host: www.reibang.com
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cookie: read_mode=day; default_font=font2; locale=zh-CN;....5b9fb068=1538705012
If-None-Match: W/"31291dc679ccd08938f27b1811f8b263"
'''
但是這樣的headers格式是個長串字符轻猖,我們需要把它改寫成params那樣的字典帆吻,手工改太麻煩也容易改錯,我們再添加一個cell使用下面代碼自動改寫(不熟悉的話可以暫時不用理解它咙边,以后隨著學習深入就很快會看懂了):
def str2obj(s, s1=';', s2='='):
li = s.split(s1)
res = {}
for kv in li:
li2 = kv.split(s2)
if len(li2) > 1:
res[li2[0]] = li2[1]
return res
headers = str2obj(headers, '\n', ': ')
發(fā)起Request請求
先用最簡單的代碼發(fā)送請求檢查是否正確:
import requests
html = requests.get(url, params=params, headers=headers)
print(html.text)
正常的話應該輸出和網(wǎng)頁源代碼差不多的內容猜煮。
獲取標題數(shù)據(jù)
在頁面一個文章上【右鍵-檢查】打開Elements元素面板,我們來仔細看每個文章標準的一段:
從圖上可以看到每個<li>
標簽對應一個文章败许,我們需要的三個內容(紅色框):
- 文章編號,
/p/0fed5efab3e5
,也是查看文章的鏈接地址,每文章唯一王带。 - 文章的標題,
人工智能通識-AI發(fā)展簡史-講義全篇
市殷。 - 文章的閱讀量愕撰,
272
因為數(shù)據(jù)都是在html標簽里面,所以我們需要使用BeautifulSoup功能模塊來把html變?yōu)槿菀资褂玫臄?shù)據(jù)格式醋寝,先嘗試抓到標題盟戏。把上面的代碼改進一下:
import requests
from bs4 import BeautifulSoup
html = requests.get(url, params=params, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
alist = soup.find_all('div', 'content')
for item in alist:
title = item.find('a', 'title').string
print(title)
全部運行輸出結果如下有9個文章:
獲取文章編號和閱讀量
我們對上面的代碼改進一下:
import requests
from bs4 import BeautifulSoup
html = requests.get(url, params=params, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
alist = soup.find_all('div', 'content')
for item in alist:
line = []
titleTag = item.find('a', 'title') #標題行a標記
line.append(titleTag['href']) #編號
line.append(titleTag.string) #標題
read = item.find('div', 'meta').find('a').contents[2]
line.append(str(int(read))) #編號,先轉int去掉空格回車,再轉str才能進line
print(','.join(line))
在這里我們使用[href]
的方法獲取了<a class="wrap-img" href="/p/0fed5efab3e5" target="_blank">
這個標記內的屬性甥桂,這個方法同樣適用于更多情況柿究,比如[class]
可以獲得warp-img
字段。
另外item.find('div', 'meta').find('a').contents[2]
這里黄选,我們利用了find
只能找到內部第一個符合條件的標記的特點蝇摸;contents[2]
這是由于a標記內包含了多個內容,<i class="iconfont ic-list-read"></i> 20
办陷,試了幾下貌夕,發(fā)覺[2]
是我們想要的內容。
以上代碼運行全部可以輸出以下內容:
獲取粉絲數(shù)和文章總數(shù)
我們把流程分成兩步走:
- 獲取文章總數(shù)和粉絲總數(shù)
- 根據(jù)文章總數(shù)循環(huán)獲取每頁的數(shù)據(jù)
把上面的cell內容都選中民镜,按Ctrl+/
都臨時注釋掉啡专。然后在這個cell上面添加一個新的cell,用來讀取文章總數(shù)acount
和關總數(shù)afocus
:
import requests
from bs4 import BeautifulSoup
html = requests.get(url, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
afuns = soup.find('div', 'info').find_all('div','meta-block')[1].find('p').string
acount = soup.find('div', 'info').find_all('div','meta-block')[2].find('p').string
afuns=int(afuns)
acount=int(acount)
print('粉絲:',afuns,'文章', acount)
find
只獲取第一個符合條件的標記制圈,find_all
是獲取所有符合條件的標記们童。
要對比著html源代碼來看:
正常應該輸出兩個數(shù)字。
獲取全部文章數(shù)據(jù)
選擇剛才屏蔽掉的代碼鲸鹦,再次按ctrl+/
恢復可用慧库。
然后修改成以下內容:
import math
import time
pages=math.ceil(acount/9)
data=[]
for n in range(1,pages+1):
params['page']=str(n)
html = requests.get(url, params=params, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
alist = soup.find_all('div', 'content')
for item in alist:
line = []
titleTag = item.find('a', 'title') #標題行a標記
line.append(titleTag['href']) #編號
line.append(titleTag.string) #標題
read = item.find('div', 'meta').find('a').contents[2]
line.append(str(int(read))) #編號,先轉int去掉空格回車,再轉str才能進line
data.append(','.join(line))
print('已獲取:',len(data))
time.sleep(1)
print('\n'.join(data))
這里使用了math.ceil(acount/9)
的方法獲取總頁數(shù)馋嗜,ceil是遇小數(shù)就進1齐板,比如ceil(8.1)
是9,ceil(9.0)
也是9,這樣即使最后一頁只有1個文章也不會被遺漏甘磨。
for n in range(1,12)
這個for循環(huán)中橡羞,每一次n都被自動加1,獲取第一頁時候n是1济舆,第二頁時候n是2...所以params['page']=str(n)
就可以自動變頁卿泽。
print('已獲取:',len(data))
這行其實沒有用,因為獲取頁面需要十幾秒鐘吗冤,如果中間不打印點什么會看上去像是無反應或死機又厉。len(data)
是指data這個列表的長度length九府。
time.sleep(1)
每讀取一頁就停1秒椎瘟,以免被服務器發(fā)覺我們是爬蟲而封禁我們。
存儲文章數(shù)據(jù)
我們上面使用逗號分開文章的序號侄旬、標題和閱讀量肺蔚,然后再加入data列表,data.append(','.join(line))
;同樣儡羔,最后我們輸出時候使用回車把data所有文章連在一起宣羊,print('\n'.join(data))
。
實際上汰蜘,我們可以直接把它存儲為excel可以讀取的.csv文件仇冯。最下面新建一個cell添加以下代碼:
with open('articles.csv', 'w', encoding="gb18030") as f:
f.write('\n'.join(data))
f.close()
這里注意,w
是write寫入模式,encoding="gb18030"
是為了確保中文能正常顯示族操。
運行后就能在你對應的Notebook文件夾內多出一個articles.csv文件苛坚,用excel打開就能看到類似下面的數(shù)據(jù):
每天使用不同的文件名存儲
我們每天爬取一下所有文章的數(shù)據(jù),每天存為一個excel表色难,都叫做articles.csv
肯定會重名泼舱。我們應該用不同的日期來命名就好很多,比如articles-201810061230.csv
表示2018年10月6日12點30分統(tǒng)計的記錄枷莉,這樣看起來就清楚多了娇昙。
如何獲取當前電腦的日期時間?計算機里面記錄時間的最簡單方法就是笤妙,只記錄從某一年開始經過了多少毫秒冒掌,大多是從1970年開始算的。只要知道距離1970年1月1日0時0分0秒0毫蹲盘,過了多少毫秒宋渔,那么這個時間就能計算出是哪年哪月哪日。
我們修改一下上面的代碼:
tm = str(int(time.time()))
fname = 'articles_' + tm + '.csv'
with open(fname, 'w', encoding="gb18030") as f:
f.write('\n'.join(data))
f.close()
這樣存儲的就是類似articles_1538722108.csv
文件名的文件了辜限。
如果要變回年月日的顯示皇拣,需要使用datatime功能模塊,例如
import time,datetime
tm=int(time.time())
print(datetime.datetime.fromtimestamp(tm).strftime('%Y-%m-%d %H:%M:%S'))
這個會輸出類似2018-10-05 14:47:11
這樣的結果
增量存儲粉絲數(shù)
我們需要把每一天抓取到的關注數(shù)和文章數(shù)存儲到一個excel表里,而不是分開的,我們新增一個cell氧急,添加如下代碼,實現(xiàn)每次運行就向total.csv
文件增加一行數(shù)據(jù):
from os.path import exists
alabels = ['time', 'funs', 'articles']
adata = [tm, afuns, str(acount)] #acount是數(shù)字颗胡,需要轉化
afname='./articles_total.csv'
if not exists(afname):
with open(afname, 'a', encoding="gb18030") as f:
f.write(','.join(alabels)+'\n')
f.close()
with open(afname, 'a', encoding="gb18030") as f:
f.write(','.join(adata)+'\n')
f.close()
exists
是存在的意思,如果文件存在吩坝,就正常往里添加新數(shù)據(jù)毒姨,如果不存在就先添加一個表頭time,focus,articles
。
最后回顧
這個文章里我們做了下面幾個練習:
- 根據(jù)問題思考需要哪些數(shù)據(jù)钉寝,能不能抓取到
- 分析頁面弧呐,找到這些數(shù)據(jù)在哪里,是html文檔里還是單獨的請求
- 找到請求嵌纲,復制headers俘枫,搞清楚params
- 發(fā)送請求,用beautifulsoup幫助找到需要的數(shù)據(jù)
- 根據(jù)文章總數(shù)逮走,循環(huán)處理分頁
- 把獲取到的數(shù)據(jù)存儲為excel可以識別的csv文件
- 利用時間自動創(chuàng)建不同的文件
- 文件的增量添加寫入鸠蚪,每次添加一行數(shù)據(jù)
最終整理到一起的代碼如下邻吭,添加了afuns澄港、aword赶诊、alike等數(shù)據(jù)帮掉。
注意必須更換自己的headers才能使用:
#cell-1 設置參數(shù)
url = 'http://www.reibang.com/u/ae784c57b353'
params = {'order_by': 'shared_at', 'page': '1'}
headers = '''
GET /u/ae784c57b353 HTTP/1.1
Host: www.reibang.com
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cookie: read_mode=day; default_font=font2; locale=zh-CN; ......b9fb068=1538705012
If-None-Match: W/"31291dc679ccd08938f27b1811f8b263"
'''
#cell-2 轉化headers
def str2obj(s, s1=';', s2='='):
li = s.split(s1)
res = {}
for kv in li:
li2 = kv.split(s2)
if len(li2) > 1:
res[li2[0]] = li2[1]
return res
headers = str2obj(headers, '\n', ': ')
# cell-3 發(fā)送整體請求论笔,獲取基本信息男公、文章總數(shù)
import requests
from bs4 import BeautifulSoup
html = requests.get(url, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
afocus = soup.find('div', 'info').find_all('div','meta-block')[0].find('p').string
afuns = soup.find('div', 'info').find_all('div','meta-block')[1].find('p').string
acount = soup.find('div', 'info').find_all('div','meta-block')[2].find('p').string
awords = soup.find('div', 'info').find_all('div','meta-block')[3].find('p').string
alike = soup.find('div', 'info').find_all('div','meta-block')[4].find('p').string
acount=int(acount)
print('>>文章總數(shù)', acount)
#cell-4 循環(huán)獲取每一頁數(shù)據(jù)
import math
import time
aread = 0
pages = math.ceil(acount / 9)
data = []
for n in range(1, pages + 1):
params['page'] = str(n)
html = requests.get(url, params=params, headers=headers)
soup = BeautifulSoup(html.text, 'html.parser')
alist = soup.find_all('div', 'content')
for item in alist:
line = []
titleTag = item.find('a', 'title') #標題行a標記
line.append(titleTag['href']) #編號
line.append(titleTag.string) #標題
read = item.find('div', 'meta').find('a').contents[2]
aread += int(read) #計算總閱讀量
line.append(str(int(read))) #編號,先轉int去掉空格回車噪伊,再轉str才能進line
data.append(','.join(line))
print('已獲取:', len(data))
time.sleep(1)
#cell-5 存儲文章數(shù)據(jù)新文件
tm = str(int(time.time()))
fname = './data/articles_' + tm + '.csv'
with open(fname, 'w', encoding="gb18030") as f:
f.write('\n'.join(data))
f.close()
#cell-6 增量存儲基礎信息
from os.path import exists
alabels = ['time', 'focus', 'funs', 'articles', 'words', 'like', 'read']
adata = [tm, afocus, afuns, str(acount), awords, alike, str(aread)]
afname='./articles_total.csv'
if not exists(afname):
with open(afname, 'a', encoding="gb18030") as f:
f.write(','.join(alabels)+'\n')
f.close()
with open(afname, 'a', encoding="gb18030") as f:
f.write(','.join(adata)+'\n')
f.close()
#cll-7 提示完成
print('>>完成囚戚,保存在%s'%fname)
過幾天收集到一些數(shù)據(jù)之后再分享數(shù)據(jù)分析相關的內容窿锉,請留意我的文章更新~
智能決策上手系列教程索引
每個人的智能決策新時代
如果您發(fā)現(xiàn)文章錯誤酌摇,請不吝留言指正;
如果您覺得有用榆综,請點喜歡妙痹;
如果您覺得很有用,歡迎轉載~
END