2019年12月7日 星期六

[LeetCode] Easy - 942. DI String Match


Source From Here
Question



How-To
  1. #!/usr/bin/env python3  
  2. class Solution:  
  3.     def diStringMatch(self, S):  
  4.         r'''  
  5.                     IDID  
  6.         IDID    -> 02143  
  7.                     III  
  8.         III     -> 0123  
  9.                     DDI  
  10.         DDI     -> 3214  
  11.   
  12.         Solution:  
  13.         Find ranges of D and reverse the numbers within that range.  
  14.         For example:  
  15.         1) Find range of D  
  16.         IDID => [(1, 2), (3, 4)]  
  17.   
  18.         2) Reverse the D range  
  19.                        --       --  
  20.             (1, 2) => 01234 -> 02134  
  21.                          --       --  
  22.             (3, 4) => 02134 -> 02143  
  23.         '''  
  24.         s_len = len(S)  
  25.         plist = list(range(s_len + 1))  
  26.   
  27.   
  28.         # 1) Collecting reverse tuple  
  29.         rt_list = []  
  30.         i = 0  
  31.         while i < len(S):  
  32.             if S[i] == 'D':  
  33.                 dpair = [i]  
  34.                 for j in range(i+1, s_len):  
  35.                     if S[j] == 'D':  
  36.                         i += 1  
  37.                     else:  
  38.                         break  
  39.   
  40.                 i += 1  
  41.                 dpair.append(i)  
  42.   
  43.                 rt_list.append(dpair)  
  44.             else:  
  45.                 i += 1  
  46.   
  47.         # 2) Work on reverse tuple  
  48.         for rt in rt_list:  
  49.             plist[rt[0]:rt[1]+1] = plist[rt[0]:rt[1]+1][::-1]  
  50.   
  51.         print('S={}; rt_list={}; plist={}'.format(S, rt_list, plist))  
  52.         return plist  
Supplement
* FAQ - How do I reverse a part (slice) of a list in Python?
>>> alist = list(range(10))
>>> alist
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> alist[1:5] = alist[1:5][::-1] # Reverse 1, 2, 3, 4 -> 4, 3, 2, 1
>>> alist
[0, 4, 3, 2, 1, 5, 6, 7, 8, 9]


2019年12月5日 星期四

[ Python 文章收集 ] 輕鬆學習 Python:透過操控瀏覽器擷取網站資料

Source From Here
Preface
這個小節延續輕鬆學習 Python:透過 API 擷取網站資料輕鬆學習 Python:透過解析 HTML 擷取網站資料 討論如何使用 Python 從第三種來源:網頁透過 selenium 套件 操控瀏覽器來擷取 HTML(全名為 HyperText Markup Language)格式的資料源,selenium 除了具備操控瀏覽器的功能還內建有以 XPath(提供在 XML/HTML 資料中以 XML 節點找尋特定資料位置的定位方法)或 CSS Selector(提供在 HTML 資料中以層疊樣式表找尋特定資料位置的定位方法)為基礎的資料解析函數。

完整的 Jupyter Notebook 內容可以參考 這裡.

關於 Selenium
我們打算求助可以操控瀏覽器的 Selenium,這是一個瀏覽器自動化的解決方案,主要應用於網頁程式測試目的;在資料科學團隊中被運用於解決擷取網站資料所碰到的問題,例如面對到需要登入、填寫表單或者點選按鈕後才會顯示出資料的網站。Python 可以透過 Selenium WebDriver 呼叫 瀏覽器驅動程式,再由 瀏覽器驅動程式 去呼叫 瀏覽器。Selenium WebDriver 對 Google Chrome 與 Mozilla Firefox 兩個主流瀏覽器的支援最好,為了確保使用上不會碰到問題,建議都使用最新版的瀏覽器、瀏覽器驅動程式與模組。

下載瀏覽器
前往官方網站下載最新版的瀏覽器。
* Google Chrome
* Mozilla Firefox


安裝 瀏覽器驅動程式 與 Selenium
前往官方網站下載最新版的 瀏覽器驅動程式,Chrome 瀏覽器的驅動程式名稱為 ChromeDriver,Firefox 瀏覽器的驅動程式名稱為 geckodriver。
* ChromeDriver
* geckodriver

下載完成以後解壓縮在熟悉的路徑讓後續的指派較為方便, 以我自身為範例, 下載並解壓縮後, 執行檔的位置在 "C:\tmp\chromedriver.exe".

接著在終端機安裝 Selenium 模組:
# pip install selenium

接著底下測試用程式碼透過 ChromeDriver 操控 Chrome 瀏覽器前往 IMDB.com 並將首頁的網址印出再關閉瀏覽器:
  1. from selenium import webdriver  
  2.   
  3. imdb_home = "https://www.imdb.com/"  
  4. driver = webdriver.Chrome(executable_path="C:/tmp/chromedriver.exe") # Use Chrome  
  5. driver.get(imdb_home)  
  6. print(driver.current_url)  
  7. driver.close()  
底下是使用 firefox 瀏覽器的範例:
  1. from selenium import webdriver  
  2.   
  3. imdb_home = "https://www.imdb.com/"  
  4. driver = webdriver.Firefox(executable_path="YOURGECKODRIVERPATH") # Use Firefox  
  5. driver.get(imdb_home)  
  6. print(driver.current_url)  
  7. driver.close()  
盤點手動操控的動作順序與 selenium 函數
測試完畢確認可以利用 Python 啟動 Chrome 以及 Firefox 瀏覽器之後,接著是盤點從 IMDB.com 前往指定電影資訊頁面過程中,手動用滑鼠、鍵盤所操控的動作:
1. 前往 IMDB.com
2. 在搜尋欄位輸入電影名稱
3. 點選搜尋按鈕
4. 將搜尋結果限縮在「電影」
5. 點選搜尋符合度最高的連結
6. 來到指定電影資訊頁面


前往 IMDB.com


在搜尋欄位輸入電影名稱


點選搜尋按鈕


將搜尋結果限縮在「電影」


點選搜尋符合度最高的連結


然後盤點會使用到的 Selenium WebDriver 方法:
driver.get() :前往 IMDB.com 首頁
driver.find_element_by_xpath() 或 driver.find_element_by_css_selector() :定位搜尋欄位、搜尋按鈕與搜尋結果連結
driver.current_url :取得當下瀏覽器的網址
elem.send_keys() :輸入電影名稱
elem.click() :按下搜尋按鈕與連結

安裝與使用 Chrome 瀏覽器外掛:XPath Helper
Selenium WebDriver 除了與 BeautifulSoup4、PyQuery 一樣支援以 CSS Selector 定位資料位址,亦支援 XPath,利用這個機會,我們簡介如何安裝與使用 Chrome 瀏覽器外掛 XPath Helper:
1. 前往 Chrome Web Store,點選外掛(Extensions)
2. 搜尋 XPath Helper 並點選加入到 Chrome 瀏覽器
3. 確認要加入 XPath Helper
4. 完成安裝



依照下列步驟使用 Chrome 瀏覽器外掛:XPath Helper:
1. 點選 XPath Helper 的外掛圖示
2. 按住 shift 鍵移動滑鼠到想要定位的元素
3. 試著縮減 XPath,從最前面開始刪減,並留意 XPath Helper 介面左邊的 XPath 與右邊被定位到的資料,尋找一個最短仍可以正確對應的 XPath


使用 Selenium 擷取多部電影資訊
接著寫作 get_movies() 函數,這個函數接受輸入電影名稱,會利用 Selenium 瀏覽到指定電影頁面,再呼叫一開始寫好的 get_movie_info() 函數,最後將多部電影的結果儲存到 Python 的 dict 中並以電影名稱作為 dict 的 key:
  1. from pyquery import PyQuery as pq  
  2. from selenium import webdriver  
  3. from random import randint  
  4. import time  
  5.   
  6. def get_movie_info(movie_url):  
  7.     """  
  8.     Get movie info from certain IMDB url  
  9.     """  
  10.     d = pq(movie_url)  
  11.     movie_rating = float(d("strong span").text())  
  12.     movie_genre = [x.text() for x in d(".subtext a").items()]  
  13.     movie_release_date = movie_genre.pop()  
  14.     movie_poster = d(".poster img").attr('src')  
  15.     movie_cast = [x.text() for x in d(".primary_photo+ td a").items()]  
  16.   
  17.     # 回傳資訊  
  18.     movie_info = {  
  19.         "movieRating": movie_rating,  
  20.         "movieReleaseDate": movie_release_date,  
  21.         "movieGenre": movie_genre,  
  22.         "moviePosterLink": movie_poster,  
  23.         "movieCast": movie_cast  
  24.     }  
  25.     return movie_info  
  26.   
  27. def get_movies(*args):  
  28.     """  
  29.     Get multiple movies' info from movie titles  
  30.     """  
  31.     imdb_home = "https://www.imdb.com/"  
  32.     driver = webdriver.Chrome(executable_path="C:/tmp/chromedriver.exe") # Use Chrome  
  33.     # driver = webdriver.Firefox(executable_path="PATHTOYOURGECKODRIVER") # Use Firefox  
  34.       
  35.     movies = dict()  
  36.     for movie_title in args:  
  37.         # 前往 IMDB 首頁  
  38.         driver.get(imdb_home)  
  39.         # 定位搜尋欄位  
  40.         search_elem = driver.find_element_by_id("suggestion-search")  
  41.         # 輸入電影名稱  
  42.         search_elem.send_keys(movie_title)  
  43.         # 定位搜尋按鈕  
  44.         submit_elem = driver.find_element_by_id("suggestion-search-button")  
  45.         # 按下搜尋按鈕  
  46.         submit_elem.click()  
  47.         # 限縮搜尋結果為「電影」類  
  48.         category_movie_elem = driver.find_element_by_xpath("//ul[@class='findTitleSubfilterList']/li[1]/a")  
  49.         # 按下限縮搜尋結果  
  50.         category_movie_elem.click()  
  51.         # 定位搜尋結果連結  
  52.         first_result_elem = driver.find_element_by_xpath("//div[@class='findSection'][1]/table[@class='findList']/tbody/tr[@class='findResult odd'][1]/td[@class='result_text']/a")  
  53.         # 按下搜尋結果連結  
  54.         first_result_elem.click()  
  55.           
  56.         # 呼叫 get_movie_info()  
  57.         current_url = driver.current_url  
  58.         movie_info = get_movie_info(current_url)  
  59.         movies[movie_title] = movie_info  
  60.         time.sleep(randint(38))  
  61.           
  62.     driver.close()  
  63.     return movies  
接著你可以測試如下:
  1. get_movies("Avengers: Endgame""Captain Marvel")  
執行結果:
  1. {'Avengers: Endgame': {'movieRating'8.5,  
  2.   'movieReleaseDate''24 April 2019 (Taiwan)',  
  3.   'movieGenre': ['Action''Adventure''Drama'],  
  4.   'moviePosterLink''https://m.media-amazon.com/images/M/MV5BMTc5MDE2ODcwNV5BMl5BanBnXkFtZTgwMzI2NzQ2NzM@._V1_UX182_CR0,0,182,268_AL_.jpg',  
  5.   'movieCast': ['Robert Downey Jr.',  
  6.    'Chris Evans',  
  7.    'Mark Ruffalo',  
  8.    'Chris Hemsworth',  
  9.    'Scarlett Johansson',  
  10.    'Jeremy Renner',  
  11.    'Don Cheadle',  
  12.    'Paul Rudd',  
  13.    'Benedict Cumberbatch',  
  14.    'Chadwick Boseman',  
  15.    'Brie Larson',  
  16.    'Tom Holland',  
  17.    'Karen Gillan',  
  18.    'Zoe Saldana',  
  19.    'Evangeline Lilly']},  
  20. 'Captain Marvel': {'movieRating'6.9,  
  21.   'movieReleaseDate''6 March 2019 (Taiwan)',  
  22.   'movieGenre': ['Action''Adventure''Sci-Fi'],  
  23.   'moviePosterLink''https://m.media-amazon.com/images/M/MV5BMTE0YWFmOTMtYTU2ZS00ZTIxLWE3OTEtYTNiYzBkZjViZThiXkEyXkFqcGdeQXVyODMzMzQ4OTI@._V1_UX182_CR0,0,182,268_AL_.jpg',  
  24.   'movieCast': ['Brie Larson',  
  25.    'Samuel L. Jackson',  
  26.    'Ben Mendelsohn',  
  27.    'Jude Law',  
  28.    'Annette Bening',  
  29.    'Djimon Hounsou',  
  30.    'Lee Pace',  
  31.    'Lashana Lynch',  
  32.    'Gemma Chan',  
  33.    'Clark Gregg',  
  34.    'Rune Temte',  
  35.    'Algenis Perez Soto',  
  36.    'Mckenna Grace',  
  37.    'Akira Akbar',  
  38.    'Matthew Maher']}}  
Supplement
Selenium WebDriver API

[LeetCode] Easy - 942. DI String Match

Source From   Here Question How-To view plain copy to clipboard print ? #!/usr/bin/env python3   class   Solution:       def di...