1. 程式人生 > >莫煩爬蟲學習記錄

莫煩爬蟲學習記錄

最初我會經常看一下自己的CSDN部落格的總訪問量、排名以及一些文章的訪問量(看著這些量與日俱增心裡很滿足,哈哈),後來想學習一下爬蟲,正好可以用來記錄自己的CSDN部落格的資訊。在Google上第一條推薦教程是莫煩,之前在網易雲上也跟著莫煩學了一下matplotlib,覺得蠻不錯,於是就跟著莫煩來學習,B站上有視訊,GitHub地址。正則表示式很重要,查閱參看小抄,另附需要轉義的字元以及

使用Python 正則匹配兩個特定字元之間的字元方法

以上鍊接有贅餘,實際上直接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 (當然還有其他的, 比如 headdelete). 剛接觸網頁構架的朋友可能又會覺得有點懵逼了. 這些請求的方式到底有什麼不同? 他們又有什麼作用?

我們就來說兩個重要的, getpost, 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)

正則表示式很重要,小抄出處