Python 利用 BeautifulSoup 爬取網站獲取新聞流

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

    •   HTML 指的是超文本標記語言 (Hyper Text Markup Language)
    •   HTML 不是一種編程語言,而是一種標記語言 (markup language)
    •   標記語言是一套標記標籤 (markup tag)
    •   HTML 使用標記標籤來描述網頁

  

  代碼實現主要分爲三個模塊:

  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

# 請不要利用爬蟲從事惡意攻擊或者違法活動

相關文章
相關標籤/搜索