0. 引言html
介紹下 Python 用 Beautiful Soup 週期性爬取 xxx 網站獲取新聞流;android
圖 1 項目介紹git
1. 開發環境github
Python: 3.6.3web
BeautifulSoup: 4.2.0 , 是一個能夠從HTML或XML文件中提取數據的Python庫*編程
( BeautifulSoup 的中文官方文檔:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/ )api
2. 介紹
瀏覽器
首先須要知道什麼是 HTML ( Hypertext Markup Language,超文本標記語言 ) :服務器
HTML 是用來描述網頁的一種語言*:app
代碼實現主要分爲三個模塊:
1. 計時 / second cnt
由於是週期性爬取,因此須要計時器來控制;
2. 設置代理 / set proxy
爲了應對網站的反爬蟲機制,須要切換代理;
3. 爬蟲 / web spider
利用 requests 請求網站內容,而後 beautifulsoup 提取出所需信息;
利用 requests.get() 向服務器發送請求 >>> 拿到 html 內容 >>> 交由 beautifulsoup 用來解析提取數據;
先看一個 requests 和 beautifulsoup 爬蟲的簡單例子:
requests :
>>> requests.get(html, headers=headers, proxies=proxies)
>>> # headers 和 proxies 是可選項 / optional
發送請求給網站,而後拿到返回的 HTML 內容,轉換爲 beautifulsoup 的對象,bs負責去提取數據;
1 from bs4 import BeautifulSoup 2 import requests 3 html = "https://www.xxx.com" 4 headers = { 5 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36' 6 } 7 proxies = "114.231.xx.xx:xxxx" 8 9 resp = requests.get(html, headers=headers, proxies=proxies) 10 resp.encoding = 'utf-8' 11 12 bsObj = BeautifulSoup(resp.content, "lxml")
本文以爬取 https://www.ithome.com/blog/ 爲例,把頁面上的新聞爬取下來,過濾掉多餘信息,拿到 新聞時間 & 新聞標題 ;
圖 2 網站界面
拿到網站的 html 代碼以後,如何剝離出本身想要的數據,參考圖 3 的流程:
圖 3 從 html 中 剝離數據的流程
首先看看 bsObj = BeautifulSoup(resp.content, "lxml") 拿到的 目標網站 ( https://www.ithome.com/blog/ ) 的 HTML 代碼,這個HTML代碼也就是瀏覽器打開網頁而後F12獲得的當前網頁的代碼;
HTML代碼描述了網頁的排版佈局文本圖片等內容,瀏覽器就是提供渲染HTML代碼的功能;
拿到的 xxxx.com 的網頁HTML代碼:
...
<div class="block"> <h2> <a href="https://www.ithome.com/html/android/380353.htm" target="_blank">魅族黃章評價滑蓋智能手機:窮途末路倒退的設計,中看不中用</a><span class="state tody">今日 14:53</span></h2> <div class="memo"> <p>昨晚榮耀Magic二、小米MIX 3手機相繼亮相,而且都採用了升降式或者滑蓋式屏幕設計。剛剛魅族黃章對於滑蓋設計表達了本身的見解:滑蓋能夠說是窮途末路倒退的設計。</p> </div> </div> </li> <li> <a class="list_thumbnail" href="https://www.ithome.com/html/discovery/380352.htm" target="_blank"> <img src="//img.ithome.com/newsuploadfiles/thumbnail/2018/8/380352_240.jpg"/> </a> <div class="block"> <h2> <a href="https://www.ithome.com/html/discovery/380352.htm" target="_blank">俄羅斯計劃「世界級」研究中心,擬克隆猛獁象</a><span class="state tody">今日 14:48</span></h2> <div class="memo"> <p>俄羅斯科學家計劃用保存的冰河時代遺骸的DNA,在一個耗資450萬英鎊的新侏羅紀公園中心克隆猛獁象</p> </div> </div> </li> <li> <a class="list_thumbnail" href="https://lapin.ithome.com/html/digi/380351.htm" target="_blank"> <img src="//img.ithome.com/newsuploadfiles/thumbnail/2018/8/380351_240.jpg"/> </a> <div class="block"> <h2>
...
而後咱們就須要在這 HTML 代碼中藉助 find() 函數 提取出咱們所須要的數據;
關於 find() 函數的參數:
# Author: coneypo # 返回查找到的內容 find(tag=None, attrs={}, recursive=True, text=None, **kwargs) # 返回查找到的內容列表 findAll(tag=None, attrs={}, recursive=True, text=None, limit=None, **kwargs) # tag 標籤參數 tag='p' # attrs 屬性參數 {'href':"www.xxx.com"} # recursive 遞歸參數 True: 遞歸向下查詢/ False: 只查詢一級標籤 # text 文本參數 text="hello world" # limit 範圍限制參數 保留查詢結果的前n項 / findall(limit=1) 等於 find() # kwargs 關鍵詞參數 id="header1"
關於 find() 函數的使用:
# 1. find(tagname) # 查找名爲 <p> 的標籤 find('p') # 2. find(list) # 查找列表中的全部標籤,<p>,<body> find(['p', 'body']) # 3. find(dict) # 查找字典中標籤名和值匹配的標籤 <a href="www.xx.com" target="_blank"> find({'target': "_blank", 'href': "wwww.xx.com"}) # 4. find('id'='xxx') # 尋找id屬性爲xxx的<h1 id="myHeader">Hello World!</h1> find('id'='myHeader')
圖 4 find() 函數的匹配
接下來就是利用 find() 一層層篩選 HTML 代碼;
首先定位到 <div class="block" >;
>>> block = bsObj.find_all("div", {"class": "block"})
<div class="block"> <h2> <a href="https://www.ithome.com/html/android/380353.htm" target="_blank"> 魅族黃章評價滑蓋智能手機:窮途末路倒退的設計,中看不中用</a>
<span class="state tody">今日 14:53</span>
</h2> <div class="memo"> <p>昨晚榮耀Magic二、小米MIX 3手機相繼亮相,而且都採用了升降式或者滑蓋式屏幕設計。剛剛魅族黃章對於滑蓋設計表達了本身的見解:滑蓋能夠說是窮途末路倒退的設計。</p> </div>
接下來就是要提取出 新聞標題 title 和 新聞時間 time;
注意 「block」 是用 find_all() 拿到的,返回的是一個 list 列表,遍歷這個列表,去找 title & time;
對於單個的 "block",好比 block[i]:
提取新聞時間:
>>> block[i].find('span', {'class': "state tody"})
<span class="state tody">今日 14:53</span>
而後
>> block[i].find('span', {'class': "state tody"}).get_text()
獲得 time 內容:
今日 14:53
提取新聞標題:
>>> block[i].find('a', {'target': "_blank"})
<a href="https://www.ithome.com/html/android/380353.htm" target="_blank">魅族黃章評價滑蓋智能手機:窮途末路倒退的設計,中看不中用</a>
而後
>> block[i].find('a', {'target': "_blank"}).get_text()
獲得 title 內容:
魅族黃章評價滑蓋智能手機:窮途末路倒退的設計,中看不中用
這樣就能夠拿到了 新聞標題 titile 和 新聞時間 time了;
3. 代碼實現
1. 計時器
這裏是一個秒數計數功能模塊,利用 datetime 這個庫,獲取當前時間的秒位,和開始時間的秒位進行比對,若是發生變化,sec_cnt 就 +=1;
sec_cnt 輸出 1,2,3,4,5,6...是記錄的秒數,能夠由此控制抓包時間;
>>> time = datetime.datetime.now() # 獲取當前的時間,好比:2018-08-31 14:32:54.440831
>>> time.second() # 獲取時間的秒位
second_cnt.py:
1 # Author: coneypo 2 # Created: 08.31 3 4 import datetime 5 6 # 開始時間 7 start_time = datetime.datetime.now() 8 tmp = 0 9 # 記錄的秒數 10 sec_cnt = 0 11 12 while 1: 13 current_time = datetime.datetime.now() 14 15 # second 是以60爲週期 16 # 將開始時間的秒 second / 當前時間的秒 second 進行對比; 17 # 若是發生變化則 sec_cnt+=1; 18 if current_time.second >= start_time.second: 19 if tmp != current_time.second - start_time.second: 20 # print("<no 60>+ ", tmp) 21 sec_cnt += 1 22 print("Time_cnt:", sec_cnt) 23 # 以 10s 爲週期 24 if sec_cnt % 10 == 0: 25 # do something 26 pass 27 tmp = current_time.second - start_time.second 28 29 # when get 60 30 else: 31 if tmp != current_time.second + 60 - start_time.second: 32 sec_cnt += 1 33 print("Time_cnt:", sec_cnt) 34 35 # 好比以 10s 爲週期 36 if sec_cnt % 10 == 0: 37 # do something 38 pass 39 tmp = current_time.second + 60 - start_time.second
2. set_proxy / 設置代理
這個網站能夠提供一些國內的代理地址: http://www.xicidaili.com/nn/ ;
由於如今網站可能會有反爬蟲機制,若是同一個 IP 若是短期內大量訪問該站點,就會被攔截;
因此咱們須要設置代理,爬這個代理網站頁面,拿到的 proxy 地址傳,給 requests.get 的 proxies 參數:
>>> requests.get(html, headers=headers, proxies=proxies)
兩個函數:
get_proxy(): 從代理網站爬取代理地址,存入本地的"proxies.csv"中;
get_random_proxy(): 從本地的"proxies.csv"中,隨機選取一個代理proxy;
注意,xicidali.com也有反爬蟲機制,親身體驗短期大量爬數據ip會被block,並且咱們一次爬取能夠拿100個代理地址,因此只須要爬一次代理網站就好了,存到本地的csv中;
要代理從本地的csv中隨機獲取proxy地址;
csv能夠按期的用 「get_proxy()」 進行更新;
get_proxy.py :
1 # Author: coneypo 2 # Created: 08.31 3 # Updated: 09.01 4 # set proxy 5 # save to csv 6 7 from bs4 import BeautifulSoup 8 import requests 9 import csv 10 import pandas as pd 11 import random 12 13 path_proxy_csv = "proxies.csv" 14 15 16 # 從 xicidaili.com 爬取代理地址,存入本地csv中 17 def get_proxy(): 18 headers = { 19 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36' 20 } 21 proxy_url = 'http://www.xicidaili.com/nn/' 22 resp = requests.get(proxy_url, headers=headers) 23 soup = BeautifulSoup(resp.text, 'lxml') 24 ips = soup.find_all('tr') 25 proxy_list = [] 26 for i in range(1, len(ips)): 27 ip_info = ips[i] 28 tds = ip_info.find_all('td') 29 proxy_list.append(tds[1].text + ':' + tds[2].text) 30 31 if len(proxy_list) == 0: 32 print("可能被屏蔽了") 33 else: 34 print("拿到的代理個數:", len(proxy_list)) 35 with open(path_proxy_csv, 'w', newline="") as csvfile: 36 writer = csv.writer(csvfile) 37 for i in range(len(proxy_list)): 38 print(proxy_list[i]) 39 writer.writerow([proxy_list[i]]) 40 41 42 # get_proxy() 43 44 45 # 從代理的 csv 中隨機獲取一個代理 46 def get_random_proxy(): 47 column_names = ["proxy"] 48 proxy_csv = pd.read_csv("proxies.csv", names=column_names) 49 50 # 新建一個列表用來存儲代理地址 51 proxy_arr = [] 52 53 for i in range(len(proxy_csv["proxy"])): 54 proxy_arr.append(proxy_csv["proxy"][i]) 55 56 # 隨機選擇一個代理 57 random_proxy = proxy_arr[random.randint(0, len(proxy_arr) - 1)] 58 59 print(random_proxy) 60 return random_proxy 61 62 # get_random_proxy()
3. get_news_from_xxx / 爬蟲
由計時器的 sec_cnt 控制爬蟲的週期 >> 每次先去 「proxies.csv」 拿代理 >> 而後交給 requests 去拿數據 >> 拿到 HTML 以後交給 beautiful 對象 >> 而後過濾出新聞流:
僞代碼以下:
1 while 1: 2 if 週期到了: 3 獲取代理地址; 4 requests 給目標網站發送請求; 5 獲取到的HTML內容交給BeautifulSoup: 6 過濾出新聞標題和時間;
get_news_from_xxx.py 完整的代碼:
1 # Author: coneypo 2 # Created: 08.31 3 # Updated: 09.01 4 # web spider for xxx.com 5 6 from bs4 import BeautifulSoup 7 import requests 8 import random 9 import datetime 10 import csv 11 import pandas as pd 12 13 14 headers = { 15 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36' 16 } 17 18 19 # 從 xicidaili.com 爬取代理地址,存入本地csv中 20 def get_proxy(): 21 headers = { 22 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36' 23 } 24 proxy_url = 'http://www.xicidaili.com/nn/' 25 resp = requests.get(proxy_url, headers=headers) 26 soup = BeautifulSoup(resp.text, 'lxml') 27 ips = soup.find_all('tr') 28 proxy_list = [] 29 for i in range(1, len(ips)): 30 ip_info = ips[i] 31 tds = ip_info.find_all('td') 32 proxy_list.append(tds[1].text + ':' + tds[2].text) 33 34 if len(proxy_list) == 0: 35 print("可能被代理網站屏蔽了", '\n') 36 else: 37 print("拿到的代理個數:", len(proxy_list), '\n') 38 with open("proxies.csv", 'w', newline="") as csvfile: 39 writer = csv.writer(csvfile) 40 for i in range(len(proxy_list)): 41 # print(proxy_list[i]) 42 writer.writerow([proxy_list[i]]) 43 44 45 # 執行一次就能夠了; 46 # 執行一次能夠更新本地代理地址; 47 get_proxy() 48 49 50 # 從存放代理的 csv 中隨機獲取一個代理 51 def get_random_proxy(): 52 column_names = ["proxy"] 53 proxy_csv = pd.read_csv("proxies.csv", names=column_names) 54 55 # 新建一個列表用來存儲代理地址 56 proxy_arr = [] 57 58 for i in range(len(proxy_csv["proxy"])): 59 proxy_arr.append(proxy_csv["proxy"][i]) 60 61 # 隨機選擇一個代理 62 random_proxy = proxy_arr[random.randint(0, len(proxy_arr)-1)] 63 64 print("當前代理:", random_proxy) 65 return random_proxy 66 67 68 # 爬取網站 69 def get_news(): 70 html = "https://www.ithome.com/blog/" 71 resp = requests.get(html, headers=headers, proxies=get_random_proxy()) 72 resp.encoding = 'utf-8' 73 74 # 網頁內容 75 bsObj = BeautifulSoup(resp.content, "lxml") 76 # print(bsObj) 77 78 # 分析 79 block = bsObj.find_all("div", {"class": "block"}) 80 # print(block) 81 82 current_titles = [] 83 current_times = [] 84 85 # analysis every block 86 for i in range(len(block)): 87 # get Time 88 time_classes = ["state tody", "state other"] 89 for time_class in time_classes: 90 tmp = block[i].find_all('span', {'class': time_class}) 91 if tmp: 92 current_time = (block[i].find('span', {'class': time_class})).get_text() 93 current_times.append(current_time) 94 95 # get Title 96 if block[i].find_all('a', {'target': "_blank"}): 97 current_title = (block[i].find('a', {'target': "_blank"})).get_text() 98 current_titles.append(current_title) 99 100 for i in range(len(current_times)): 101 print(current_times[i], current_titles[i]) 102 return current_times, current_titles 103 104 105 get_news() 106 107 # 計時 108 start_time = datetime.datetime.now() 109 tmp = 0 110 sec_cnt = 0 111 112 while 1: 113 current_time = datetime.datetime.now() 114 115 # second 是以60爲週期 116 # 將開始時間的秒second / 當前時間的秒second 進行對比 117 if current_time.second >= start_time.second: 118 if tmp != current_time.second - start_time.second: 119 # print("<no 60>+ ", tmp) 120 sec_cnt += 1 121 print("Time_cnt:", sec_cnt) 122 if sec_cnt % 10 == 0: 123 get_news() 124 print('\n') 125 tmp = current_time.second - start_time.second 126 127 # when get 60 128 else: 129 if tmp != current_time.second + 60 - start_time.second: 130 sec_cnt += 1 131 print("Time_cnt:", sec_cnt) 132 133 if sec_cnt % 10 == 0: 134 get_news() 135 print('\n') 136 tmp = current_time.second + 60 - start_time.second
最終的輸出 log:
1 拿到的代理個數: 100 2 3 當前代理: 111.155.124.73:8123 4 今日 13:53 你應該跳過三星Galaxy Note 9等待S10的5個理由 5 今日 13:49 索尼CEO迴應聯機爭議:PS上最好玩,爲什麼要跨平臺? 6 今日 13:43 順豐冷運/買6送4,陽澄湖鮮活六月黃大閘蟹10只旗艦店58元 7 今日 13:35 華爲Mate 9、P10系列GPU Turbo升級已全面開放 8 今日 13:35 自如熊林:沒有意願「對付」誰,是自如的責任定承擔 9 今日 13:33 《暗黑4》?暴雪尚有一個未公佈的3A級動做遊戲 10 今日 13:12 經典重生:Palm新Logo曝光 11 今日 13:07 安卓系統各版本最新份額:「奧利奧」接近15%,6.0、7.0佔大頭 12 今日 12:49 新研究稱:世界低估了中國對全球科學的貢獻 13 今日 12:48 韓春雨接受校方調查結果:論文存缺陷,研究過程不嚴謹 14 08月31日 中糧出品/寧夏中衛原產,悅活優選100枸杞蜂蜜454g×2瓶39.9元 15 今日 12:35 手機QQ iOS版v7.7.8正式版更新:新增多款高顏值濾鏡 16 今日 12:23 網遊總量調控,遊戲暴利時代或終結 17 今日 12:12 直降30元,網易雲音樂氧氣耳機開學季大促99元 18 今日 12:08 美團點評IPO訂價區間敲定:每股60-72港幣 19 今日 12:05 索尼HX9九、HX95卡片相機發布:大變焦、能拍4K視頻 20 今日 11:30 自如迴應「阿里員工租甲醛房去世」:需客觀全面調查 21 今日 11:30 夏末清倉,森馬企業店純棉印花T恤19.9元包郵 22 今日 11:27 1198元!vivo Y81s正式開售:千元機也有高顏值 23 今日 11:24 豬的器官能移植給人類嗎?科學家研究出了測試程序 24 今日 11:16 小米8/MIX 2S開始推送MIUI 10穩定版:全新系統UI 25 今日 11:11 2030年8億人將被機器人取代,但這些職業竟還能保住飯碗! 26 08月31日 直降400元,汪峯FIIL隨身星Pro降噪耳機799元新低開搶 27 今日 11:01 生存遊戲《人渣》現納粹紋身引批評,開發商移除並致歉 28 今日 10:56 雷柏外設開學季大促:機械鍵盤低至94元 29 今日 10:46 蘋果AR開發的下一個動做:Maps地圖應用 30 今日 10:46 屏幕邊框已死,筆記本設計迎來新時代 31 今日 10:45 可口可樂CEO喝夠了可樂,因而51億美圓買下COSTA咖啡 32 今日 10:41 放棄悟空問答,今日頭條的第一次戰略性撤退 33 今日 10:40 狂甩不掉,ZTM M8防水運動藍牙耳機旗艦店29.9元(40元券) 34 今日 10:36 MagicScroll可變形平板電腦問世:採用卷軸式柔性屏 35 今日 10:34 等谷歌牌手錶的能夠散了,Pixel Watch今年不會有 36 今日 10:27 佳能全幅無反相機EOS R規格、照片全泄露了 37 今日 10:07 IFA2018:主打設計!微星發佈P65 Creator筆記本電腦 38 今日 10:01 翻版「藥神」:代購印度抗癌藥後加價銷售,12人被判刑 39 今日 9:56 359元, 西部數據My Passport 1T移動硬盤開學季大促新低 40 今日 9:43 媒體:ofo已拖欠雲鳥、德邦等多家物流供應商億元欠款 41 今日 9:40 巴菲特:蘋果若收購特斯拉,我會支持 42 08月31日 雙星旗艦店網面運動鞋69→39元、啄木鳥彈力牛仔褲109→59元 43 今日 9:31 NASA稱國際空間站打補丁成功,氣壓保持穩定 44 今日 9:29 疑似華爲Mate 20 Pro真機現身!三攝設計大變樣 45 今日 9:25 一波曝光以後,谷歌Pixel 3/3 XL已現身FCC網站 46 今日 9:24 《賽博朋克2077》可獨行出擊可雙人行動,還能夠買房 47 今日 9:19 2899元,索尼 PlayStation 4 Pro 遊戲主機秒殺新低 48 今日 9:17 河北科大:未發現韓春雨團隊主觀造假 49 今日 9:12 米粉平均月收入7485元?網友:算上雷軍了吧 50 08月28日 3000mAh大容量,耐時旗艦店8節5號/7號鋰鐵電池19.9元 51 今日 8:56 網傳順豐3億條數據在暗網出售,官方迴應:非順豐數據 52 今日 8:49 出版署嚴控網遊總量,騰訊網易股價大跌 53 今日 8:46 爲何接你電話的客服老是解決不了問題? 54 Time_cnt: 1 55 Time_cnt: 2 56 Time_cnt: 3 57 Time_cnt: 4 58 Time_cnt: 5 59 Time_cnt: 6 60 Time_cnt: 7 61 Time_cnt: 8 62 ...
你就能夠每隔 10s 刷新下該網站上面的新聞流;
刷新週期在 line 87:
>>> if sec_cnt % 10 == 0:
# 請尊重他人勞動成果,轉載或者使用源碼請註明出處:http://www.cnblogs.com/AdaminXie
# 項目源碼上傳到了個人 GitHub,若是對您有幫助,歡迎 Star 支持下:https://github.com/coneypo/Web_Spider
# 請不要利用爬蟲從事惡意攻擊或者違法活動