上課前可以用pip裝一下 requests-html
pip install requests-html
待會會用到這個外部套件 🚀
python 要 3.6以上才能用
蛤?
例如 Google 日曆 API 可以讓你用程式取得,建立,修改,刪除行事曆資料。
然後你就可以以此開發各種服務
我們先用 Reqres 這個網站來看一下效果
Request 那裏的是要呼叫的網址,Response 那裏則是你會收到的內容(通常是JSON格式)
/api/users?page=2 後面的 ?page=2 是什麼意思?
如果是 GET request,在網址最末端加上 '?' 後,可以繼續附帶參數(parameter), 以這裡來說,GET /api/users?page=2 就是取得第二頁的使用者的意思,如果他的使用者資料有1000000筆,為避免一次回傳過多資料, 通常我們會用page來讓別人能分批取得使用者資料。
若看到 GET /api/users?page=2&gender=male&age=20 意思就會是: 取得第二頁且性別為男性且年齡為20的使用者。
(不過Reqres這個網站沒提供這種進階搜尋的功能就是了)
給大家 5~10 分鐘,大家可以操作一下上面的 Reqres 這個網站
使用NY Times API 取得暢銷書歷史紀錄
先取得 API key
找Book API -> GET /lists/best-sellers/history.json
他有提供很方便的測試環境,只要在 左側 API key 那裏輸入剛才拿到的 key,即可取得結果。
import json
import requests
api_key = '你剛拿到的 api-key'
url = 'https://api.nytimes.com/svc/books/v3/lists/best-sellers/history.json?api-key=' + api_key
r = requests.get(url)
r.encoding = 'utf-8'
# print(r.text)
data = json.loads(r.text)
print(data['num_results'])
31425
[補充]
為了避免有人瘋狂發API拖慢系統,有些(多數) API 因為要商用或有驗證身份的需求,會要求你先註冊一個帳號來取得一個 API Key,這時你就需要在發送 request 時一併將這個 API Key 發送給對方才能得到資料。 這類 API 通常會限制每小時的使用次數。
請先閱讀這份 Weather API ,再編寫出一支程式 print 出台北今天(7/18)的天氣為何。
可以先找到台北的 woeid,再使用後面查詢天氣的API。
注意:請了解 GET /api/location/(woeid)/ 後回傳的資料的意義,可以先觀察他回傳的資料,再嘗試將該天氣print出來。
練完你就會串基本的API了 🙌
首先,瀏覽器要取得資料必須要向伺服器發送請求 (request),發完後可能得到html或json或各種格式的內容
所以下面 requests 程式碼的意義是這樣,他使用名為 "requests" 的"python套件"來發送 GET request
另外你不能用瀏覽器上面的網址列按 Enter 來發POST,只能用來發 GET
因為 這是 reqres.in 這個網站的 API,所以只有reqres這個網站能用
其他網站的 API 不見得是這樣設計的,即使是這樣他們通常也會因為你沒權限把你擋下來## 另外 從字典取值,好像很多人會轉不過來,這裏做個補充
first_dict = {
'dog_name': 'Incredible boy',
'cat_name': 'Dory'
}
print(first_dict['dog_name'])
another_dict = {
'room1': {
'dog_name': 'Cerberus',
'cat_name': 'Diego'
}
}
print(another_dict['room1']['dog_name'])
Incredible boy Cerberus
# 再複雜一點
other_dict = {'star_lab': [
{
'room1': {
'dog_name': 'Crash',
'cat_name': 'Eddie'
}
},
{
'room2': {
'dog_name': 'Indominus rex',
'cat_name': 'Indoraptor'
}
}
]}
print(other_dict['star_lab'][0]['room1']['dog_name'])
Crash
接下來你會學到幾件事
一隻程式,透過自動瀏覽網際網路並下載資料,可用於編纂網路索引來建立搜尋引擎。
蛤??
記得早上的 Request Response 流程嗎? 你也可以從response中取得網頁的內容 (html)
比方說你嘗試用上面的方法取得 Wiki 百科 某一頁
你得到的 html 裡面通常會有很多超連結,而當爬蟲程式看到超連結時,也可以選擇順手把這些超連結存下來,等爬(載)完這一頁之後,再取得其他超連結的資料。
爬完這一頁後再跳到下一頁,然後再重複... 直到爬完整個 Wiki
或你電腦爆掉 💥
所以才叫爬蟲。
當然,有些網站不希望別人去爬取他的內容,因此他會在網頁最上層(some_url/robots.txt)放個 robots.txt
例如知乎的 robots.txt 放在https://www.zhihu.com/robots.txt
除了上面提到的 robots.txt 之外,一般爬蟲也不建議"太過頻繁"的爬取特定網頁的內容,這樣的行為可能會使對方網站阻塞。
通常爬蟲發送request的頻率建議控制在 1秒 1次,以不超過人類的操作速度為佳。
它能像"requests 套件"一樣發送 http request(其實他把requests套件包在裡面), 也能解析 response 回傳 html的字串,取得想要的資訊
[補充]
除了 request_html 之外,類似功用的套件還有 beautifulsoup,不過 beautifulsoup 只有解析 html 字串的功能,發送 request 還是要靠 "requests 套件"
pip install requests_html
步驟 1: 開啟開發人員工具,查看 Element 資訊
確認 "滑鼠右鍵 > 檢查網頁原始碼"內的內容跟 "瀏覽器開發人員工具"的內容 是否一致。
若不做處理,一般來說爬蟲只會取得 "滑鼠右鍵 > 檢查網頁原始碼" 內的 html資訊,有時這資訊會與瀏覽器開發人員工具"的內容不同
步驟 2: 引用Package,取得 html string
from requests_html import HTMLSession
session = HTMLSession()
response = session.get('http://quotes.toscrape.com/')
print(response) # status_code, example: 200, 404...
print(response.html.text) # 印出網頁上所有的文字
# print(response.html.html) # 印出網頁的 html
<Response [200]> Quotes to Scrape Quotes to Scrape Login “The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.” by Albert Einstein (about) Tags: change deep-thoughts thinking world “It is our choices, Harry, that show what we truly are, far more than our abilities.” by J.K. Rowling (about) Tags: abilities choices “There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.” by Albert Einstein (about) Tags: inspirational life live miracle miracles “The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.” by Jane Austen (about) Tags: aliteracy books classic humor “Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.” by Marilyn Monroe (about) Tags: be-yourself inspirational “Try not to become a man of success. Rather become a man of value.” by Albert Einstein (about) Tags: adulthood success value “It is better to be hated for what you are than to be loved for what you are not.” by André Gide (about) Tags: life love “I have not failed. I've just found 10,000 ways that won't work.” by Thomas A. Edison (about) Tags: edison failure inspirational paraphrased “A woman is like a tea bag; you never know how strong it is until it's in hot water.” by Eleanor Roosevelt (about) Tags: misattributed-eleanor-roosevelt “A day without sunshine is like, you know, night.” by Steve Martin (about) Tags: humor obvious simile Next → Top Ten tags love inspirational life humor books reading friendship friends truth simile Quotes by: GoodReads.com Made with ❤ by Scrapinghub
依照 Selector 語法 (也稱為 CSS Selector, 更多語法可參考CSS Selector)
# 找 所有 的 p element, 像<p>oxz</p>
select_string = 'p'
# 找 所有 id 為 'some_id' 的 element, 像 <span id='some_id'>texttext</span>
select_string = '#some_id'
# 找 所有 class 為 some_class 的 element,像 <span class='some_class'>texttext</span>
select_string = '.some_class'
# 找 所有 屬性有 some_attr,且值為 some_value element,像 <span some_attr=some_value>texttext</span>
select_string = '[some_attr=some_value]'
# 找 所有 <p> element, 且 id 為 'some_id', 像 <p id='some_id'>texttext</p>
select_string = 'p#some_id'
# 找 所有 <p> element, 且 屬性有 some_attr,且值為 some_value,像 <p some_attr=some_value>texttext</p>
select_string = 'p[some_attr=some_value]'
# 找 所有 <p> element, 且 class 是 text,像 <p class='text'>sample</p>
select_string = 'p.text'
# 找 所有 <p> element,同時 p 的 class 有 text 也有 link,像 <p class='text link'>sample</p>
select_string = 'p.text.link'
element = response.html.find(select_string) # 這一行使用上述 select_string 來取得符合指定 pattern 的element
## 多層搜尋
# 找在 p 這個 element 裡面,class 為 text 的 element,像 <p><span class='text'>sample</span></p>
element = response.html.find('p .text')
# 找在 "class 為 some_class 的 div" 裡的 "p element" 裡面,且 class 為 text 的 element
# 像 <div class='some_class'><p><span class='text'>sample</span></p></div>
element = response.html.find('div.some_class p .text')
# 先找 class 為 some_class 的第一個 div,再從中找所有 class 為 link 的 <a ...> element
element = response.html.find('div.some_class')[0].find('a.link')
# 若要找第一個作者
from requests_html import HTMLSession
session = HTMLSession()
response = session.get('http://quotes.toscrape.com/')
# 取得 class 為 quote 的 element 裡面 的第一個 a element
element = response.html.find('.quote a', first=True)# first=True 意思是 只回傳"第一個"符合這格式的 element
print(element)
print(element.text) # 印出該 element 內包含的文字
print(element.attrs) # 印出該 element 內包含的屬性
print(element.attrs['href'])
print(element.absolute_links)
<Element 'a' href='/author/Albert-Einstein'> (about) {'href': '/author/Albert-Einstein'} /author/Albert-Einstein {'http://quotes.toscrape.com/author/Albert-Einstein'}
from requests_html import HTMLSession
session = HTMLSession()
response = session.get('http://quotes.toscrape.com/')
elements = response.html.find('[itemprop=text]')
for element in elements:
print(element.text)
“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.” “It is our choices, Harry, that show what we truly are, far more than our abilities.” “There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.” “The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.” “Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.” “Try not to become a man of success. Rather become a man of value.” “It is better to be hated for what you are than to be loved for what you are not.” “I have not failed. I've just found 10,000 ways that won't work.” “A woman is like a tea bag; you never know how strong it is until it's in hot water.” “A day without sunshine is like, you know, night.”
任務: 印出第一個 Quote 所有"Tag"的超連結
結果應該要印出
/tag/change/page/1/
/tag/deep-thoughts/page/1/
/tag/thinking/page/1/
/tag/world/page/1/
hint:
from requests_html import HTMLSession
session = HTMLSession()
response = session.get('http://quotes.toscrape.com/')
elements = response.html.find('.quote')[0].find('.tag')
for element in elements:
print(element.attrs['href'])
/tag/change/page/1/ /tag/deep-thoughts/page/1/ /tag/thinking/page/1/ /tag/world/page/1/
# Sample Answer
from requests_html import HTMLSession
session = HTMLSession()
response = session.get('http://quotes.toscrape.com/')
link_set = set()
elements = response.html.find('.quote span a')
for element in elements:
link_set.add(element.attrs['href'])
print(link_set)
{'/author/Jane-Austen', '/author/Andre-Gide', '/author/Eleanor-Roosevelt', '/author/Steve-Martin', '/author/Albert-Einstein', '/author/Thomas-A-Edison', '/author/Marilyn-Monroe', '/author/J-K-Rowling'}
任務: 從 imdb 取得 The Darkest Minds (2018) 這部電影的導演
A: Jennifer Yuh Nelson
若有需要,請參考request_html文檔 取得需要的 method 資訊。
# Sample Answer
from requests_html import HTMLSession
session = HTMLSession()
response = session.get('https://www.imdb.com/movies-coming-soon/2018-08/')
element = response.html.find('[itemprop=director] a[itemprop=url]')[1]
print(element.text)
Jennifer Yuh Nelson
挑戰題:
A: Bradley Whitford, Mandy Moore, Gwendoline Christie, Amandla Stenberg
特殊的網站像是:
註: 絕大多數的資料可以透過 API 或基礎爬蟲學到的方法取得。
現在在這裡
有時是兩種東西造成的
Form (表單) 可以讓你輸入內容,而當你點擊下方的 Submit按鈕,通常會有另一隻Javascript程式將表單中的內容透過 Request(POST)發送給遠端伺服器,而遠端伺服器將新的內容回傳之後,Javascript 再將網頁中的內容直接修改,因此網址不會有變化。
有些則是當你點擊按鈕後,他一樣會發個 request,但他會將你導向到別的網址(像火車時刻表)
也就是說我們透過觀察開發人員工具中的"Network"來查看他發送了什麼request,再依樣畫葫蘆發一樣的request給對方,通常我們就能得到我們要的資料。
使用 Post 來取得 7/20 22:00 出發,台南到台北的高鐵車號
備註: 高鐵有 [API](https://ptx.transportdata.tw/PTX),以後有需要請直接使用API不要這樣直接爬,這裏只是拿來做 post 的練習。打開開發人員工具的Network頁籤,觀察點擊"立即查詢"後會發佈什麼 Request
# Sample Answer
from requests_html import HTMLSession
session = HTMLSession()
form_data = {
'startStation': '9c5ac6ca-ec89-48f8-aab0-41b738cb1814',
'endStation': '977abb69-413a-4ccf-a109-0272c24fd490',
'theDay': '2018/07/20',
'timeSelect': '22:00',
'waySelect': 'DepartureInMandarin',
}
url = 'https://m.thsrc.com.tw/tw/TimeTable/SearchResult'
response = session.post(url, data=form_data) #### 這裏改用 POST 了
elements = response.html.find('a.ui-block-a')
for element in elements:
print(element.text)
0696 0294
先想想. . .
hint: 也是Request
概念也是跟剛才一樣觀察登入時會發的Request,再用程式發一樣的內容。
一般來說,等登入成功後,通常對方會給你個cookie(用來暫存資料),你只要將cookie存下來,下次 GET 網頁時一併將這個cookie給對方即可。
模擬登入來取得資料,確認登入後收到的html資料裡有 Logout 字樣
注意,多數網站不會想讓開發者能用機器人來登入,所以才會有驗證碼或什麼Receptra(我不是機器人)。
因此請先確認 /robots.txt 中有無限制使用權限,不過這個網站沒有放。
# Sample Answer
from requests_html import HTMLSession
session = HTMLSession()
form_data = {
'username': 'Neo',
'password': 'quote*2018*some_static_word'
}
url = 'http://quotes.toscrape.com/login'
login_response = session.post(url, data=form_data) # 先發登入用的 POST request!
print(login_response)
url = 'http://quotes.toscrape.com/'
second_response = session.get(url, data=form_data) # 登入成功後再 GET 一次!
print(second_response.html.find('.col-md-4 p a', first=True).text)
<Response [200]> Logout
先想想. . . . . . .
因為這些網頁在瀏覽器收到 html後,還要用Javascript跑一下才會得到你在開發人員工具看到的最終版本 (例如像臉書那樣,只要滑鼠不斷向下捲動就會不斷跑出新內容的網站)
先想想
請取得 Data Science and Artificial Intelligence Practice 課程網站中,2/27的Description的內容
(Course Introduction and Basics of...)
from requests_html import HTMLSession
session = HTMLSession()
url = 'https://sirius207.github.io/course-template/2018/'
response = session.get(url)
response.html.render() # 加上這一行即可,第一次跑他會花幾分鐘的時間下載 Chromium,用來跑 Javascript
elements = response.html.find('td[data-title=Description]')
print(elements[0].text)
Course Introduction and Basics of Supervised/Unsupervised Learning Slides: Course introduction Slides: Supervised unsupervised Learning
# 註,這個似乎 requests_html 的render 會 render 不完全,如果想知道怎麼取的話可以參考下面這份舊版簡報的內容
# https://nbviewer.jupyter.org/format/slides/github/x-village/python-course/blob/master/Lesson08-Web%20Crawler/Lesson08-Crawler-old.ipynb#/8/18