Posts match “ Python ” tag:

str()型態

以下都是str()型態,而非unicode。

  • "我是中文"
  • abc
  • 'abc' + '我是中文'

unicode型態

  • u'我是中文'
  • u'abc'
  • u'abc' + u'我是中文'

注意事項

  • 如果要用str.format()進行字串格式化,如果字串是unicode型態,要指定字串型態為unicode才能呼叫format(),例如: u'Hi, {}\n'.format("我是大白"),否則將會出錯。
  • str 和 unicode 直接相加會產生錯誤訊息,例如: u'abc' + '我是中文'將會出現「UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)」錯誤訊息。
  • unicode型態的字串如果要寫入檔案,則要先做encoding,例如file.write(u'我是中文'.encode('UTF-8'))
  • 如果噴出
    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
    
    則可以將所有str型態的利用unicode(string, 'utf8')轉成unicode型態。

摘要

IPython是一個互動式的python interpreter,這邊介紹幾個常用的小技巧。
如果系統尚未安裝,則執行 sudo pip install ipython來安裝(在這之前python要先裝好!),一起體驗它的威力。

筆記大綱

  1. 查看屬性和方法
  2. 編輯程式碼
  3. 神奇指令
  4. 呼叫作業系統指令

查看屬性和方法

  1. [TAB] 列出所有可用的子物件和方法,例如輸入os.後按下[TAB]會顯示os底下所有的可用函式。

如果輸入os.[TAB]沒反應,那就是你忘了import os!!!

In [6]: os.[TAB]
Display all 218 possibilities? (y or n)
os.EX_CANTCREAT      os.chdir             os.nice
os.EX_CONFIG         os.chmod             os.open
os.EX_DATAERR        os.chown             os.openpty
os.EX_IOERR          os.chroot            os.pardir
os.EX_NOHOST         os.close             os.path
os.EX_NOINPUT        os.closerange        os.pathconf
os.EX_NOPERM         os.confstr           os.pathconf_names
os.EX_NOUSER         os.confstr_names     os.pathsep

這方法只能用在查詢,不能自動補齊有點可惜,要有[TAB]自動補齊功能我推薦bpython,除了列出所有可用成員外,也可以直接按[TAB]選取,相當方便,如下圖。
bpython-tab.jpg

  1. %pdoc 模組模組 ? 會出現該模組或方法的文件,%psource 模組%pfile 模組模組 ?? 會顯示程式碼。以下是輸入os??的結果:
Type:       module
String Form:<module 'os' from '/usr/lib/python2.7/os.pyc'>
File:       /usr/lib/python2.7/os.py
Docstring:
OS routines for Mac, NT, or Posix depending on what system we're on.

This exports:
  - all functions from posix, nt, os2, or ce, e.g. unlink, stat, etc.
  - os.path is one of the modules posixpath, or ntpath
  - os.name is 'posix', 'nt', 'os2', 'ce' or 'riscos'
  - os.curdir is a string representing the current directory ('.' or ':')
  - os.pardir is a string representing the parent directory ('..' or '::')
  - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\')
  - os.extsep is the extension separator ('.' or '/')
  - os.altsep is the alternate pathname separator (None or '/')
  - os.pathsep is the component separator used in $PATH etc
  - os.linesep is the line separator in text files ('\r' or '\n' or '\r\n')
  - os.defpath is the default search path for executables
  - os.devnull is the file path of the null device ('/dev/null', etc.)

Programs that import and use 'os' stand a better chance of being

編輯程式碼

輸入edit可以直接進入編輯器,這邊的編輯器是採用系統環境變數EDITOR的設定,我這邊是vim,那麼就會開啟vim來編輯。

def calculate_mode(freq):
    """
    找出眾數。
    :param freq:
    :return:
    """
    # 先找出最高頻率

    highest_freq = max(freq.values())

    # 如果某個數字的頻率最高,則加入眾數

    mode = [number for number, freq in freq.items() if freq == highest_freq]
    if not (1 <= len(mode)):
        mode = None
    else:
        mode.sort()
    return mode

神奇指令

像上面所提到的%pdoc, %psource, %pfile等等指令都是IPython的神奇指令(magic command),這些指令都是用%開頭,如果automagic是開啟的狀態,就可以直接輸入指令而不用在另外加%,一般預設是開啟,不知道是否有開啟就直接輸入任一個神奇指令看是否會出錯。 要關閉就輸入automagic,要開啟就輸入%automagic

以下列一些常用的神奇指令:

  • %magic 查看所有的神奇指令。
  • %edit 編輯並執行檔案。
  • %edit -x filename 編輯但不執行檔案。
  • %edit 5:20 filename 編輯檔案第5行、第20個字元。
  • %pwd 顯示目前目錄。
  • %time statement 計算程式碼執行的時間。

更完整的指令可以查詢這個IPython Quick Ref Sheets

呼叫作業系統指令

如果要在IPython呼叫系統指令,只需要在指令面前加入!就好,例如要執行ping指令。

In [9]: !ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=45 time=12.9 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=45 time=13.3 ms

學生時期的一些程式作品集,整理一下拿出來獻醜 >///<(以下圖多!!)。
主要是用Java寫的,當然還有含Android和Python,而應用主要有資料庫、網路爬蟲、Socket Programming、機器學習應用、演算法設計...等等項目。

以下是簡單列表(照完成時間排序):

  • Gmail Man - Gmail客戶端,可以跟本機端資料庫做同步更新。
  • 餐飲熱量計算程式 - 學校專題(補)飲食熱量資料庫應用。
  • 股市交易資料爬蟲 - 網路爬蟲可以抓每日股市的交易資料。
  • 圖片下載程式 - 輸入關鍵字,可以自動下載搜尋到的所有圖片。
  • 樂透網路下注程式 -- 幫學弟寫的一個網路程式設計作業,半個小時內寫完。
  • 地址顯示服務 - 輸入一個網址,提取一個網頁正文並分析出地址,然後把所有地址顯示在網頁上。
  • 在地遊App - 一支可以查詢地點資訊、顯示照片和地圖的App。
  • 地點關鍵字提取引擎 - 利用文字分析技術自動提取出一個地點的關鍵字列表。
  • 地點自動分類器 - 實作一個分類演算法來將地點自動分類為景點、餐飲和住宿。

Gmail Man

這是校內「雲端程式設計課程」的作業之一,題目是用抽的,我抽中用Gmail Contact API去寫一個應用程式,我就簡單做一個Gmail的客戶端。

功能

Gmail的客戶端,可以直接編輯通訊錄的資料,並且可以跟本機端的資料庫做同步備份。

所用技術


餐飲熱量計算程式

這程式是我的學校畢業專題,因為我大四休學,所以原本一年的專題中斷(做臉部器官偵測,是影像處理的應用),後來復學後就做這個簡單的應用當作畢業專題。

功能

程式提供各種餐飲的熱量資訊,使用者可以點選三餐所吃的餐點即可立刻計算當天攝取的熱量總和;或者可以輸入熱量和一些限制(例如:低脂、素食...等),程式會找出符合的餐飲。。

  • 主畫面1:使用者可以點選他今天三餐吃的餐點,程式就會幫忙計算今天攝取的總熱量是多少。 輸入熱量值和選擇限制,程式會幫你挑出符合的餐飲讓使用者選。

  • 主畫面2:可以直接檢視每個餐飲在資料庫的資料,必要的時候可以在程式登入資料庫然後編輯資料。

  • 主畫面3:或者更直接可以執行SQL指令和設定過濾器來做查詢資料。

所用技術


股市交易資料爬蟲

功能

這是一隻網路爬蟲,用來從網頁上抓每天股票買賣超的交易資料,並且把當日的前十名買賣超的公司+張數做成圖表顯示出來。

所用技術


圖片下載程式

功能

輸入關鍵字(例如:正妹),程式會自動幫你下載所有符合的圖片(使用Google搜尋結果)。

所用技術


樂透網路下注程式

幫學弟寫的作業程式,30分鐘搞定。

這是一個Network Socket Program,Server端可以等待多個Client來連線下單,然後接著時間截止後,Server端會停止繼續下注,並且把中獎號碼送到Client端,如果Client端接受後有中獎,則會顯示。

所用技術


地址顯示服務

功能

此網頁服務允許使用者輸入一個網頁,後端會分析出網頁正文後並且使用文字分析提取出所有地址,接著把地址標註在地圖上。 此服務主要目的在於當我們瀏覽一個美食部落格時,該文章當中可能列舉一個區域的所有美食,我們就可以在地圖上一次瀏覽所有美食店家的位置。

所用技術


在地遊App

功能

此App可以用來查詢和編輯地點資料,顯示該地點的照片和拍新照片並上傳,顯示地圖位置。

所用技術


地點關鍵字提取引擎

功能

此文字探勘引擎使用字詞加權技術,以每個地點的部落格文章作為分析來源,找出屬於該地點的關鍵字並且計算出該字詞和該地點的相關程度。如下圖所示,使用了四種不同的字詞加權計算公式來計算,我們可以比較不同的公式所算出來的精準度並且衡量計算速度。

所用技術


地點自動分類器

功能

此程式使用KNN分類演算法來將地點以它所有的部落格作為分析來源來自動分類為景點、餐飲和住宿。

所用技術

  • KNN分類演算法
  • KNN分類演算法加速器

最近聊天機器人Bot這股趨勢才慢慢要崛起,之前做了一點觀察紀錄,這次進入實作階段,首先就直接使用Facebook Message Platform做為開始。

其實Facebook官方文件寫的也蠻清楚的,只是有一些技巧要注意。

前置作業

  1. 先有一個粉絲團:粉絲團就是用來當作Bot的身份,只支援粉絲團對一般使用者聊天而已,先確定你有一個粉絲團是用來讓Bot綁定的,這邊可以選擇你既有的粉絲團或是新建立一個,如果是新建立的,粉絲團新建立完後可以先不發佈,但是如果要開始測試Bot的話,就要發佈了。
  2. 準備開發環境和專案:新建立一個Python專案,並且將我們會用到的套件:FlaskRequests安裝好。
  3. 申請一個FB應用程式:去facebook for developer建立或選擇一個應用程式,如果是新建立的話,應用程式類型選擇「網站」即可,建立完之後在主控台左側功能表找到「Messager」點下。

4.為自己的網域名稱申請SSL:Facebook要求所有Messager呼叫的API都是要走https,所以這步驟是必要的(我也覺得比較麻煩的地方),這邊我用的是Let's Encrypt來申請,依照「Installing Client Software」的步驟來進行,先用git clone把專案複製下來,然後在終端機使用certbot-auto工具來產生憑證,我是用Apache HTTP Server,所以執行

sudo ./certbot-auto --apache -d YOUR_DOMAIN_NAME.COM

YOURDOMAINNAME.COM 要換成你自己的網域名稱,不用加http://開頭,接著依照它的指示做即可。

憑證產生好後,我們需要將憑證複製到我們的API專案內,我這邊是在專案內建立一個 ssl 的資料夾,並且使用終端機切換目錄到 /etc/letsencrypt/live/YOURDOMAINNAME.COM/ (這邊 YOURDOMAINNAME.COM 記得代換成自己的網域名稱)裡面,將 fullchain.pemprivkey.pem 複製到剛剛建立的 ssl 資料夾,這等等會用到,千萬別複製錯檔案,我一開始就是複製到 cert.pem / privkey.pem,然後後面就出錯了(看下圖)。

OK,到目前為止,前置步驟算完成,我們可以開始寫程式了。


程式撰寫

程式的部分主要分成三個主要步驟:

  1. 設定Webhook做驗證。
  2. 從Facebook接收使用者的訊息。
  3. 傳遞訊息到Facebook。

1. 設定Webhook

新增一個可以接收驗證碼的HTTP GET方法,裡面的 I_AM_VERIFICATION_CODE 要換成自己的,其他 hub.* 開頭的都是Facebook既定好的,要照著寫。

@app.route(API_ROOT + FB_WEBHOOK, methods=["GET"])
def fb_webhook():
    verification_code = 'I_AM_VERIFICIATION_CODE'
    verify_token = request.args.get('hub.verify_token')
    if verification_code == verify_token:
        return request.args.get('hub.challenge')

再回到Facebook應用程式主控台,按下Setup Webhooks會出現下圖的視窗,要將你的API網址 (API_ROOT + FB_WEBHOOK) 和上面的I_AM_VERIFICATION_CODE分別輸入進去,然後勾選你要的訊息功能:

勾選訂閱項目如下:

  • message_deliveries: 訊息傳遞的相關報表。
  • messages: 最基本的訊息接收和傳遞。(最基本一定要勾選的)
  • messaging_optins: 允許使用者使用在你網站上的Send-to-Messager按鈕,並且可以授權傳遞訊息到這。
  • messaging_postbacks: 允許你傳遞除了文字之外的訊息種類,例如:按鈕和按鈕按下後的事件。

2. 接收訊息。

從Facebook傳遞過來的文字訊息JSON格式如下:

{
    "object": "page",
    "entry": [
        {
            "id": 1719080561637302,
            "time": 1463187309603,
            "messaging": [
                {
                    "sender": {
                        "id": 1176454272400076
                    },
                    "recipient": {
                        "id": 1719080561637302
                    },
                    "timestamp": 1463187309521,
                    "message": {
                        "mid": "mid.1463187309223:ae45fc642d87298297",
                        "seq": 23,
                        "text": "嗨,你好。"
                    }
                }
            ]
        }
    ]
}

可以從 entrymessaging 取得每一則訊息傳送者和內容,取得訊息程式如下,使用HTTP POST,然後去解JSON即可:

@app.route(API_ROOT + FB_WEBHOOK, methods=['POST'])
def fb_handle_message():
    message_entries = json.loads(request.data.decode('utf8'))['entry']
    for entry in message_entries:
        messagings = entry['messaging']
        for message in messagings:
            sender = message['sender']['id']
            if message.get('message'):
                text = message['message']['text']
                print("{} says {}".format(sender, text))
    return "Hi"

3. 傳遞訊息。

傳遞訊息也很簡單,只需要呼叫Graph Message API即可,程式碼如下:

def send_fb_message(to, message):
    post_message_url = 'https://graph.facebook.com/v2.6/me/messages?access_token={token}'.format(token=config.FB_TOKEN)
    response_message = json.dumps({"recipient":{"id": to}, 
                                   "message":{"text":message}})
    req = requests.post(post_message_url, 
                        headers={"Content-Type": "application/json"}, 
                        data=response_message)
    print("[{}] Reply to {}: {}", req.status_code, to, message)

裡面的 config.FB_TOKEN 是我們在前置作業新增的FB應用程式所產生的,要去應用程式的主控台去找。(config模組要另外新增喔!)

再來要綁定這個FB應用程式和你Bot的粉絲團,開啟終端機執行

curl -ik -X POST "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=ACCESS_TOKEN"

上面的 ACCESS_TOKEN 要用剛剛的粉絲專頁存取權杖複製代換,執行完成功後會出現 {“success”: true}

最後一步,我們要啟動我們的API,加入執行API的程式:

if __name__ == '__main__':
    context = ('ssl/fullchain.pem', 'ssl/privkey.pem')
    app.run(host='0.0.0.0', debug=True, ssl_context=context)

這邊的context所用到是我們前置作業所複製出來的兩個憑證,為了是讓我們的API是用https存取,這也是Facebook Message Platform所要求的。

最後執行API來讓我們跟Bot聊天吧。

完成程式碼如GitHub連結https://github.com/enginebai/Facebook-Message-Bot 請給星星吧,我需要你們的支持~

常見問題可能的解法

1.Callback網址有問題?先用工具測試你的Webhook GET API是否正常運作,可以呼叫Webhook API然後給 hub.verify_tokenhub.challege 這兩個參數,照我們寫的程式正確會回傳 hub.challege 的值。

2.訊息沒有收到或傳遞失敗?別忘了要綁定Facebook應用程式和粉絲團(執行 curl 那一段)
如果你還有其他疑問,歡迎留言來詢問。


此文章同步分享於我的Medium

我是大白,Co-founder & Developer @DualCores Studio,歡迎對於Bot開發、人工智慧、語意分析、機器學習有興趣的朋友多跟我一起交流學習,接下來會更專注分享相關的題目。

今天來分享一個可以從網路獲取資料的技術:叫「網路爬蟲」,英文稱做Web Crawler or Web Scapying,以下簡稱爬蟲,這篇文章將會分成「一、原理介紹」以及「二、程式實作」,如果有基本概念的朋友們,可以直接跳至「二、程式實作」開始,我將會以iPeen愛評網(以下簡稱iPeen)作為範例用Python來實作簡單的爬蟲,希望透過此篇入門介紹,能讓大家都會寫基本的爬蟲,抓到你想要的資料。

一、基本概念

爬蟲是一個將網路上的檔案下載下來然後做一些處理的程式,一般我們在網路上看到的網頁、圖片或影片等,都算是一個檔案,只是透過瀏覽器我們可以看到正確的結果,例如:網頁檔案本身是一個HTML檔案,瀏覽器幫我們轉成我們看得懂的網頁介面(如下圖所示,操作方式為在Chrome按右鍵的[檢視網頁原始碼])。

為何要講這個呢?
因為爬蟲所要處理的,是沒有透過瀏覽器處理的HTML原始碼 ,當我們看到網頁有我們要抓的資料之時(以iPeen為例,我們要抓店家的資訊,如下圖,操作方式為在網頁上游標移至店家名稱連結,然後按下右鍵按[檢查])

我們必須知道這些在網頁上我們所看到的店家名稱是在HTML原始碼所對應的位置(如下圖),必須看看這些這些程式碼有沒有什麼樣的規律或特徵,以剛剛所講述的店家網址名稱為例,我們可以看出店家連結是<a>標籤,擁有 data-label=”店名” 獨特可以找出店家連結的CSS屬性。

爬蟲兩大工作

  1. 下載檔案:在給定一個網址時,我們可以使用HTTP協定的方法去對於伺服器送出Request請求,並且取得Response回應,在我們爬蟲一般的情況就是對於伺服器送出GET Request來取得HTML檔案的Response,在取得HTML檔案後接著我們就可以進行第二步驟:分析內容。
  2. 分析內容:擁有HTML之後,我們就可以透過搜尋、正則、字串處理、切分、取代等的各種技巧來將HTML過濾抓到我們所要的資料,例如上述例子所提到的店家連結標籤,可以用正則找到<a>標籤,然後用搜尋過濾留下CSS包括data-label=”店名” 的標籤,然後再用正則去除標籤只留下連結和店家名稱。

二、程式實作

首先,我們先安裝必要的第三方程式套件,有了這些套件加上Python本身提供的套件可以加速我們的開發工作,這邊我們使用Python3來開發,上述爬蟲兩大工作「下載檔案」是使用Requests來實作,而「分析內容」是使用BeautifulSoup來實作,這兩個套件安裝方式可以參考官網,不再文章詳述,讓我們開始吧!

我們的目標是抓下iPeen的美食店家資訊,在我們開始寫程式之前,我們必須大致分析網頁結構,知道我們所要的資料是在哪一些頁面,爬蟲要從哪裡開始、有哪些網頁要爬、爬蟲要怎麼到那些網頁等,所以先在iPeen先找到可以抓店家列表的網頁作為爬蟲起始點,這邊我們找到「http://www.ipeen.com.tw/search/taiwan/000/1-0-0-0/」具有店家列表可以作為爬蟲起始點,從此頁面取得各店家的資訊頁面連結後,在個別進入每個店家頁面抓店家資訊。

開頭我們先寫一個方法從起始點抓到所有的店家資訊頁面連結,並且儲存到列表中,程式碼如下(完整版在 commit (6741fd)[https://github.com/enginebai/iPeenCrawler/commit/6741fde67ea829544c01712d9f8aa76625789135]):

def get_shop_link_list():
    list_req = requests.get(LIST_URL)
    if list_req.status_code == requests.codes.ok:
        soup = BeautifulSoup(list_req.content, HTML_PARSER)
        shop_links_a_tags = soup.find_all('a', attrs={'data-label': '店名'})

        shop_links = []
        for link in shop_links_a_tags:
            shop_link = ROOT_URL + link['href']
            print(shop_link)
            shop_links.append(shop_link)
            parse_shop_information(shop_link)

我們先使用 requests.get() 來下載HTML程式碼 list_req.content 並且傳入 BeautifulSoup() 作為分析來源,然後我們可以用BeautifulSoup.find_all(‘a’, attrs={‘data-label’: ‘店名’})
找到所有CSS為 ‘data-label’: ‘店名’<a> 標籤。

接著,我們針對每個店家頁面去分析出詳細資訊,程式碼如下(完整版在 commit fd6435a):

def parse_shop_information(shop_link):
    shop_id = re.sub(re.compile(r'^.*/' + SHOP_PATH), '', shop_link).split('-')[0]
    print(shop_id)

    req = requests.get(shop_link)
    if req.status_code == requests.codes.ok:
        soup = BeautifulSoup(req.content, HTML_PARSER)
        shop_header_tag = soup.find('div', id='shop-header')
        name_tag = shop_header_tag.find('span', attrs={'itemprop': 'name'})
        print(re.sub(SPACE_RE, '', name_tag.text))
        category_tag = shop_header_tag.find("p", class_={'cate i'})
        print(re.sub(SPACE_RE, '', category_tag.a.text))
        address_tag = shop_header_tag.find('a', attrs={'data-label': '上方地址'})
        print(re.sub(SPACE_RE, '', address_tag.text))

        gps_str = address_tag['href']
        # print(gps_str)

        gps_str = re.search('/c=(\d+.\d*),(\d+.\d*)/', gps_str).group().replace('/', '')
        # print(gps_str)

        lat = gps_str.split(',')[0]
        lng = gps_str.split(',')[1]
        print(lat.split('=')[1], lng)

這邊我們可以看到用 BeautifulSoup.find() 再搭配正則 re.search() / re.sub() 和字串操作 string.split() / string.replace() 就可以取得店家的名稱、分類、地址、GPS(分析取得字串方法不只一種,你可以寫的跟我不同沒關係,重點在於可以有效且正確分析出字串即可)。

大功告成,所有完整程式碼在我的GitHub上,歡迎下載使用,也多用星星來支持。

FAQ

  1. 寫爬蟲有一個問題很多人會來問我「奇怪,我的網頁程式碼有看到,為何爬蟲卻無法抓到?」,這個原因很簡單,因為你看到的是「假的」(無誤),是因為網頁內容是動態產生的(以用Javascript產生的內容為例),你所看到的是經過瀏覽器已經將Javascript執行後產生的結果,而一般爬蟲預設是沒有執行這些Javascript,所以當然抓不到,我們要如何判別呢?在Chrome當中如果是右鍵按下[檢視網頁原始碼],則你所看到的HTML是沒有動態產生內容的,也就是我們一般爬蟲預設抓到的資料;在網頁上游標移動到其中一個元素按右鍵點[檢查]所看到的HTML內容則是經過動態產生的結果,一般爬蟲沒有特別處理是無法得到這些內容的。 這問題有沒有解?答案是有的,需要用其他方式例如讓爬蟲先模擬瀏覽器取得動態產生內容後,再去取得分析HTML的方式解決。
  2. 爬蟲是否能抓到一個需要登入或驗證後的網頁資訊?答案是可以的,但前提是你要知道該網頁怎麼登入、怎麼驗證身份,接著像瀏覽器一樣用 Session / Cookie 將登入或驗證後的資料儲存一起帶入到須登入驗證的網頁中去爬。
  3. 有時候網頁會回傳 HTTP 429 Too Many Requests的錯誤,我該如何處理?會出現這錯誤表示你存取得太頻繁,每一個爬蟲都是一個Request,對於伺服器都是一個資源損耗,有些伺服器會擋甚至你存取太快會視為攻擊的一種(類DDOS),正確的解法是調整爬蟲抓網頁的速度,可以設定延遲機制來預防。

後記

附上我在Android讀書會所分享「Android x 網路爬蟲」的完整簡報內容:SlideShare

我是大白,Co-founder & Developer @DualCores Studio / Developer @17 Media],歡迎對於Bot開發、人工智慧、語意分析、機器學習有興趣的朋友多跟我一起交流學習,接下來會更專注分享相關的題目。