莫煩爬蟲學習記錄
最初我會經常看一下自己的CSDN部落格的總訪問量、排名以及一些文章的訪問量(看著這些量與日俱增心裡很滿足,哈哈),後來想學習一下爬蟲,正好可以用來記錄自己的CSDN部落格的資訊。在Google上第一條推薦教程是莫煩,之前在網易雲上也跟著莫煩學了一下matplotlib,覺得蠻不錯,於是就跟著莫煩來學習,B站上有視訊,GitHub地址。正則表示式很重要,查閱參看小抄,另附需要轉義的字元以及
以上鍊接有贅餘,實際上直接re.findall(r'\'(.*)\'', string)就可以string中單引號及其之間的內容,.*在正則表示式中表示匹配除了'\n'之外的任何字元0次或無限次,在DOTALL中也可以匹配'\n'。
在學完3.3節之後我成功爬取了自己部落格的資訊。
1.1瞭解網頁結構
知曉HTML的內容有超連結、標題、段落以及相應標籤……
from urllib.request import urlopen import re # if has Chinese, apply decode() 有中文,就要用到decode(),讀取結果是網頁的HTML,urllib是python自帶模組 html = urlopen("https://morvanzhou.github.io/static/scraping/basic-structure.html").read().decode('utf-8') print(html) # 爬取HTML中的title res = re.findall(r"<title>(.+?)</title>", html) print("\nPage title is: ", res[0]) # 爬取段落<p> res = re.findall(r"<p>(.*?)</p>", html, flags=re.DOTALL) # re.DOTALL if multi line print("\nPage paragraph is: ", res[0]) # 爬取網頁中所有超連結 href res = re.findall(r'href="(.*?)"', html) print("\nAll links: ", res)
2beautifulsoup解析網頁:基礎
安裝beautifulsoup直接pip install beautifulsoup4 官方文件有中文文件和英文文件
安裝之後執行會報錯:
bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library?
錯誤原因是python3沒有該直譯器(解析器知識),在cmd安裝之即可:pip install lxml。參考
程式碼:
from bs4 import BeautifulSoup
from urllib.request import urlopen
# if has Chinese, apply decode()
html = urlopen("https://morvanzhou.github.io/static/scraping/basic-structure.html").read().decode('utf-8')
# print(html)
# 將HTML要載入進 BeautifulSoup, 以 lxml的這種形式載入. 除了 lxml, 其實還有很多形式的解析器, 不過大家都推薦使用 lxml 的形式.
#然後 soup 裡面就有著這個 HTML 的所有資訊. 如果你要輸出 <h1> 標題, 可以就直接 soup.h1.
soup = BeautifulSoup(html, features='lxml')
print(soup.h1)
print('\n', soup.p)
all_href = soup.find_all('a') # 這一行找到的是所有 <a>的資訊,若想要單純的連結,還需進一步操作
print(all_href)
all_href = [l['href'] for l in all_href] # 列表解析,這裡才是去掉冗餘資訊之後的連結資訊,這是lxml的功勞
print('\n', all_href)
2.2beautifulsoup網頁解析 CSS
左圖是HTML ,右圖是HTML+CSS,CSS可以使網頁變得豐富多彩. 文字有了顏色, 字型, 位置也多樣了,整個佈局會很好看。
CSS 主要用途就是裝飾你 “骨感” HTML 頁面. 如果將 HTML 比喻成沒穿衣服的人, 那 CSS 就是五顏六色的衣服. 穿在人身上讓人有了氣質. CSS 的規則很多, 好在如果你只是需要爬網頁, 你並不需要學習 CSS 的這些用法或規則, (如果你想, 你可以看到這裡), 你只需要注意 CSS 的CLASS就可以了,之後可以用CLASS的資訊來篩選HTML中想要的內容了。
from bs4 import BeautifulSoup
from urllib.request import urlopen
# if has Chinese, apply decode()
html = urlopen("https://morvanzhou.github.io/static/scraping/list.html").read().decode('utf-8')
print(html)
soup = BeautifulSoup(html, features='lxml')
# 找到其中tag是<li>的資訊,並且有class=month
month = soup.find_all('li', {"class": "month"})
for m in month:
print(m.get_text())
# 找到其中tag是<ul>的資訊,並且有class=month
jan = soup.find('ul', {"class": 'jan'})
d_jan = jan.find_all('li') # 在jan中找到tag是<li>的資訊
for d in d_jan:
print(d.get_text())
2.3beautifulsoup解析網頁:正則表達
正則表示式能用簡單的規則匹配到多樣化的文字資訊,在這一部分學習前有必要看下它的教程,不需要完全記住,需要的時候回來看一下。正則表示式要在前面加r否則就是普通字串:比如ptn = r'r[au]n'是一個正則表示式,它能匹配到ran和run,而ptn = 'r[au]n'就只是一個單純的字串。。。。。好吧,我試了下下面兩種都匹配成功,不報錯,不加r也能匹配
import re
string1 = 'dog runs to cat'
print(re.search('r[au]n', string1)) # <_sre.SRE_Match object; span=(4, 7), match='run'>
print(re.search(r'r[au]n', string1)) # <_sre.SRE_Match object; span=(4, 7), match='run'>
from bs4 import BeautifulSoup
from urllib.request import urlopen
import re
# if has Chinese, apply decode()
html = urlopen("https://morvanzhou.github.io/static/scraping/table.html").read().decode('utf-8')
print(html)
soup = BeautifulSoup(html, features='lxml')
# 找到所有tag是<img>資訊,並且其src屬性中含有子串'.jpg',屬性是通過lxml解析到的
# 如這一行: <img src="https://morvanzhou.github.io/static/img/course_cover/scraping.jpg">
img_links = soup.find_all("img", {"src": re.compile('.*?\.jpg')}) # 正則表示式
for link in img_links:
print(link['src'])
# 找到所有tag是<img>資訊,並且其src屬性中含有子串'https://morvan.',屬性是通過lxml解析到的
# 如這一行: <a href="https://morvanzhou.github.io/tutorials/data-manipulation/scraping/">
course_links = soup.find_all('a', {'href': re.compile('https://morvan.*')}) # 正則表示式
for link in course_links:
print(link['href'])
2.4小練習:爬百度百科
#Here we build a scraper to crawl Baidu Baike from this page onwards. We store a historical webpage that we have already visited to keep tracking it.
from bs4 import BeautifulSoup
from urllib.request import urlopen
import re
import random
# Select the last sub url in "his", print the title and url.
base_url = "https://baike.baidu.com" # 百度百科
his = ["/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711"] # 網路爬蟲
# Find all sub_urls for baidu baike (item page), randomly select a sub_urls and store it in "his". If no valid sub link is found, than pop last url in "his".
url = base_url + his[-1] # -1是選取列表中的最後一個元素
# Put everthing together. Random running for 20 iterations. See what we end up with.
html = urlopen(url).read().decode('utf-8')
soup = BeautifulSoup(html, features='lxml')
print(soup.find('h1').get_text(), 'url: ', his[-1]) # find是選擇第一次出現的'h1',find_all是選擇所有出現的'h1'
# find valid urls 獲取網頁內tag是<a>,且屬性target中有_blank, 說或者href中有正則表示式裡指定內容的有效地址(通過分析網頁的HTML,選擇適合的正則表示式,來獲取目標內容)
sub_urls = soup.find_all("a", {"target": "_blank", "href": re.compile("/item/(%.{2})+$")}) #
if len(sub_urls) != 0: # 如果爬取到地址
his.append(random.sample(sub_urls, 1)[0]['href']) # 隨機抽取一個url追加到his--history random.sample(obj, 1)是從obj中隨機抽取一個值
else: # no valid sub link found如果沒爬取到地址
his.pop() # 刪除his中最後一個元素
print(his)
# 以上是試驗,下面真正開始前進20次,看整個過程都爬取到些什麼
his = ["/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB/5162711"] # 初始化 his指向'網路爬蟲'條目的URL
for i in range(20):
url = base_url + his[-1]
html = urlopen(url).read().decode('utf-8') # 讀取網頁HTML內容
soup = BeautifulSoup(html, features='lxml') # 用lxml解析器解析之
print(i, soup.find('h1').get_text(), ' url: ', his[-1]) # i次爬取 爬取的HTML標題 HTML的網頁地址
# find valid urls
sub_urls = soup.find_all("a", {"target": "_blank", "href": re.compile("/item/(%.{2})+$")}) # 爬取有效連結
if len(sub_urls) != 0: # 爬取到地址就隨機將其一追加至his
his.append(random.sample(sub_urls, 1)[0]['href'])
else:
# no valid sub link found
his.pop() # 刪除his中最後一個元素
3.1post登入cookies
之前 我們通常使用 Python 的自帶模組 urllib, 來提交網頁請求. 這個模組能滿足我們大部分的需求, 但是為了滿足你日益膨脹的其他需求, 比如向網頁傳送資訊, 上傳圖片等等, 我們還有一個偉大的 Python 外部模組 requests, 來有效的處理這些問題.
獲取網頁的方式
其實在載入網頁的時候, 有幾種型別, 而這幾種型別就是你開啟網頁的關鍵. 最重要的型別 (method) 就是 get
和 post
(當然還有其他的, 比如 head
, delete
). 剛接觸網頁構架的朋友可能又會覺得有點懵逼了. 這些請求的方式到底有什麼不同? 他們又有什麼作用?
我們就來說兩個重要的, get
, post
, 95% 的時間, 你都是在使用這兩個來請求一個網頁.
post
- 賬號登入
- 搜尋內容
- 上傳圖片
- 上傳檔案
- 往伺服器傳資料 等
get
- 正常開啟網頁
- 不往伺服器傳資料
這樣看來, 很多網頁使用 get
就可以了, 比如 莫煩Python 裡的所有頁面, 都是隻是 get
傳送請求. 而 post
, 則是我們給伺服器傳送個性化請求, 比如將你的賬號密碼傳給伺服器, 讓它給你返回一個含有你個人資訊的 HTML.
比如下圖:請求http://pythonscraping.com/pages/files/form.html時用的方法是get,填寫firstname和lastname,提交資料之後,請求的是http://pythonscraping.com/pages/files/processing.php,用的方法是post,資料來源是我們在上一個頁面填寫的內容。於是在下面程式碼中可以看到,我們在請求後一個網頁的時候,給他傳入資料,就能完成這個post方法。
cookies那裡不知道為什麼提交不成功,session那裡profile頁面已經陣亡了,沒成功
import requests
import webbrowser
# 在百度中 https://www.baidu.com/s?wd=%E8%8E%AB%E7%83%A6Python
param = {"wd": "莫煩python"}
r = requests.get('http://www.baidu.com/s', params=param) # params引數的值將會與前面一段組合成完整的URL,完成在百度中搜索'莫煩python'
print(r.url)
# webbrowser.open(r.url) # 在預設瀏覽器中開啟網址
# 帶資料的post
data = {'firstname': 'Morvan', 'lastname': 'Zhou'}
r = requests.post('http://pythonscraping.com/pages/files/processing.php', data=data)
'''在預設瀏覽器中開啟網址,這裡以為網頁會顯示Hellow, Morvan Zhou!結果不是,結果與在瀏覽器中直接開啟
http://pythonscraping.com/pages/files/processing.php的結果是一樣的,我想著可能就如後面莫煩所說cookies
一樣,下面這一行與上一行已經不是一個連續的過程了,下面一行就相當於是提交了空資訊,下面傳檔案也是類似的情況'''
# webbrowser.open(r.url) # r.url = 'http://pythonscraping.com/pages/files/processing.php'
print(r.text)
# 傳檔案的post
file = {'uploadFile': open(r'C:\Users\WW\Pictures\mine\successful.jpg', 'rb')}
r = requests.post('http://pythonscraping.com/pages/files/processing2.php', files=file)
# webbrowser.open(r.url) # 在預設瀏覽器中開啟網址,顯示提交失敗,因為這不是連續的過程,這一次開啟就相當於提交了空資訊
print(r.text)
# payload = {'username': 'Morvan', 'password': 'password'}
# r = requests.post('http://pythonscraping.com/pages/cookies/welcome.php', data=payload)
# print(r.text) # 沒成功 logged in wrong 不曉得為什麼
# print(r.cookies.get_dict()) # cookies儲存了我們的使用者名稱和密碼的資訊
# r = requests.get('http://pythonscraping.com/pages/cookies/profile.php', cookies=r.cookies) # 這個連結好像已經不支援了
# print(r.text)
# 每次登陸網頁,當需要到下一個請求的時候就要重新登陸,很麻煩;於是程式設計師用一連串的session(會話)來控制cookies,這就比較簡單了,不用傳入cookies的資訊,而是以登陸的狀況來訪問頁面
session = requests.Session()
payload = {'username': 'Morvan', 'password': 'password'}
r = session.post('http://pythonscraping.com/pages/cookies/welcome.php', data=payload)
print(r.cookies.get_dict())
r = session.get("http://pythonscraping.com/pages/cookies/profile.php") # 這個頁面已經不被支援了
print(r.text)
3.2下載檔案
# 去網頁的HTML中找到檔案地址然後下載,可用幾種不同方法下載
import os
save_path = r'H:\learning like never feel tired\Scraping python\3.2download_pics'
os.makedirs(save_path, exist_ok=True)
# 檢視HTML後得到網頁地址
IMAGE_URL = "https://morvanzhou.github.io/static/img/description/learning_step_flowchart.png"
# Download the image url using urlretrieve
from urllib.request import urlretrieve
urlretrieve(IMAGE_URL, save_path + r'\image1.png') # whole document
# Using requests.get to download at once
import requests
r = requests.get(IMAGE_URL)
with open(save_path+ r'\image2.png', 'wb') as f:
f.write(r.content) # whole document
# Set stream = True in get() function. This is more efficient.
r = requests.get(IMAGE_URL, stream=True) # stream loading
with open(save_path + r'\image3.png', 'wb') as f:
for chunk in r.iter_content(chunk_size=32):
f.write(chunk)
3.3小練習:下載國家地理美圖
試了一下國家地理會接受瀏覽器的安全檢查,導致讀到的HTML是安全檢查的HTML,不是我們想要的,於是我將目標網站換成了中國國家地理網站,目標是下載下面這些圖:
圖片對應的標籤如下,解析時發現依然只用ul和class就能提取到屬於目標圖片的ul部分,id屬性可以忽略:
我在莫煩的原始碼上稍做了些修改:
# Download amazing pictures from national geographic
from bs4 import BeautifulSoup
import requests
URL = "http://www.dili360.com/" # 中國國家地理網站
# find list of image holder
html = requests.get(URL).text
print(html)
soup = BeautifulSoup(html, 'lxml')
img_ul = soup.find_all('ul', {"class": "style-1"}) # 用ul和class提取目標圖片所屬的HTML
print(img_ul)
# Create a folder for these pictures
import os
save_path = r'H:\learning like never feel tired\Scraping python\3.3download_btf_pics'
os.makedirs(save_path, exist_ok=True)
# Find all picture urls and download them.
for ul in img_ul:
imgs = ul.find_all('img') # 找到目標片段中的img標籤
name_lst = ul.find_all('h4') # 用首頁看到的圖片名為圖片命名
for img, name in zip(imgs, name_lst):
url = img['src'] # 在img標籤中選擇'src'屬性,得到對應圖片的url
r = requests.get(url, stream=True) # 用上一節stream loading方法來下載圖片,從地址中獲取檔案流
# image_name = url.split('/')[-1] # 用url中圖片名字作為儲存圖片的名字
image_name = str(name).split(r'>')[1].split('<')[0]+'.jpg' # 分割字串,並重新拼接成.jpg形式的圖片名
with open(save_path+r'\%s' % image_name, 'wb') as f:
for chunk in r.iter_content(chunk_size=128):
f.write(chunk)
print('Saved %s' % image_name)
正則表示式很重要,小抄出處: