2021年4月27日 星期二

Git - 刪除 submodule

Git - delete submodule

因為上網 Google 這問題你會發現這個Stackoverflow這個Github的答案。很久以前查的時候 stackoverflow 置頂的答案是以前最多 upvote 的答案,也就是 githb 的答案。但 git 在版本更新後那個 7 步刪除 submodule 的已經過時了。雖然還是可以用但現在三步就可完成。
    # 用 git submodule deinit 清除 git config 的紀錄
    git submodule deinit -f path/to/submodule

    # 手動刪除 git 不再 track 的 submodule file
    rm -rf .git/modules/path/to/submodule

    # 用 git 清除 .gitmodules 的紀錄
    git rm -f path/to/submodule
參考資料 :
1.stackoverflow

2021年4月26日 星期一

Google API - Python 學習筆記 - Upload post

Google Blogger API Table

其實找到好用的 Google API document 就完成 80% 了,這裡紀錄 Python 連結。再紀錄個 Google 給開發者用的 Playground,可以讓你先試試 API。

Post article

這裡紀錄程式碼模擬平常的流程,但其實可以只用 insert 就完成所有事。
  • New Post
    新增草稿。
        # Create a new draft post.
        # The return value (newpost) is dict contain all info of this draft
        newpost = service.posts().insert(blogId=BLOGID, isDraft=True).execute()
  • Update content
    Update 好 content 後,要轉成 JSON object。以下若程式碼直接傳 JsonPost(str),會回傳錯誤 Invalid JSON payload received. Unknown name “”: Root element must be a message,即使他們 print 出來一模一樣。
        # Update some content of the new draft post.
        newpost['title'] = "posted via python"
        newpost['content'] = "<div>hello world test</div>"
        JsonPost = json.dumps(newpost, indent=4, ensure_ascii=False)
        service.posts().update(blogId=BLOGID, postId=newpost['id'], 
            body=json.loads(JsonPost)).execute()
  • Publich
    其實就只是把 Draft 的狀態改成 Publish。
        # Publish the new post.
        service.posts().publish(blogId=BLOGID, postId=newpost['id']).execute()
  • Insert 解決一切
    可以直接用 insert 就好,上面的程式碼只是模擬。
        service.posts().insert(blogId=BLOGID, body=body).execute()
參考資料 :
1.Google API Library

2021年4月13日 星期二

Google API - Python 學習筆記

Google API - python ver.

紀錄一些使用 python 呼叫 Google API 的心得

基本前置作業

  • Google Account
    首先你要有 google 帳號。
  • Google Cloud Platform project
    想要使用 Google Cloud Platform (GCP) 的 service,你需要創建一個 GCP project。左上角 project name 右邊的倒三角按下去新增。
  • Enable API
    旁邊的 API&Services => Library => 新增你想要的服務。不過我當初沒有用這步,應該是我用 Oauth 而非 API Keys 的關係 ( 完整介紹 )。
  • Credentials
    產生金鑰,我是選 Desktop App,然後下載其 json 檔就樣就好。雖然 Google Guide 其實有給很多講解及步驟,但我沒做也能運行。

安裝 google python api library

  • 安裝 google api python
    首先安裝 google-api-python-client,點進去有安裝方法,這裡用 Linux 做些紀錄。基本上是安裝一個 virtualenv,讓他新增一個獨立且乾淨的 python 執行環境,然後利用這虛擬環境去下載他門的 library。
        # 安裝虛擬 python 環境和 google-api-python-client
        pip install virtualenv
        virtualenv <your-env>
        source <your-env>/bin/activate
        <your-env>/bin/pip install google-api-python-client
  • 安裝 google_auth_oauthlib
    再來安裝 Oauth 2.0
        <your-env>/bin/pip install google_auth_oauthlib

安裝 google python api library

我寫這篇的主因,因為 google python api library 已經不再更新,有些 sample code 已經過時不能使用所以在這裡紀錄一些可用的 sample code。
  • Sample Code - flow
    這是 Google Oauth API Scope 我拿 blogger 做測試。完整的程式碼在 sample.py,輸出結果在 output.json。這裡擷取片段,順序還要參考完整的程式碼。 
        # 首先是連線設置,Flow 這個 class 是用來讀取你的 Credentials 來認證你的程式。
        # client_secrets.json = 你下載的 Oauth json file。
        # SCOPES = 你宣告要使用哪些功能 ( ex. gmail, blogger... )
        from google_auth_oauthlib.flow import InstalledAppFlow
        SCOPES = ['https://www.googleapis.com/auth/blogger']
        flow = InstalledAppFlow.from_client_secrets_file(
            'client_secrets.json', SCOPES)
    
        # 用 port 0 連線 GCP,這裡會用預設瀏覽器去做認證,結束後會跟你說可以關閉網頁
        creds = flow.run_local_server(port=0)
  • Sample Code - token
    這裡回傳值 creds,是 GCP 給的暫時性 token,若你的 token 還在且未過期,你就可以用此 token 直接連線而不用再透過瀏覽器認證。
        # 如果你有儲存 token,可以直接用 token 重新連線
        # token expired 的話且沒過太就的話可以用 creds.refresh(Request()) 做 refresh token
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
        if not creds.valid:
            if creds.expired and creds.refresh_token:
                creds.refresh(Request())
  • Sample Code - build
    這裡依照 Blogger API Guide 去做取資料。
        # 先依照 scope 取得服務,再依照 api 的格式去取得資料
        # posts() 跟 list() 都是依照 Blogger API Guide 去填寫的,最後執行 execute()
        # 回傳會是 dict,要自己轉 json
        from googleapiclient.discovery import build
        service = build('blogger', 'v3', credentials=creds)
        posts = service.posts().list(blogId=BLOGID,maxResults=3).execute()
參考資料 :
1.我跟Google官網

2021年4月11日 星期日

JQuery - $( document ).ready()

JQuery - $( document ).ready()

鑒於 $( document ).ready() 有縮寫,導致初學者如我看不懂,這裡紀錄一下。
  • 基本版本
    $( document ).ready() 基本上是等 Document Object Model ( DOM ) 好了後,可以執行 JavaScript code 時啟動。另外常見的 $( window ).on( "load", function() { ... }) 則是等整個文件都處理完畢後 ( 包含 image, iframe...等等 ) 才執行,而非只有 DOM。
        // A $( document ).ready() block.
        $( document ).ready(function() {
            console.log( "ready!" );
        });
  • 縮寫版本
    老練的開發者會寫 $() 代替 $( document ).ready(),但最好不要。
        // Shorthand for $( document ).ready()
        $(function() {
            console.log( "ready!" );
        });
參考資料 :
1.JQuery_Doc

Python Error - UnicodeDecodeError: 'cp950' codec can't decode

讀檔時 UnicodeDecodeError: 'cp950' codec can't decode

老實說這非常常見,即使你的檔案是用 UTF-8 編寫,而且用 Python3 ( 絕大部分 default 是 utf-8 ),仍會報這個錯。所以要避免程式能在不同平台都能正常使用,讀檔時最好都加上 encoding="utf-8"
    with open("somefile.py", encoding="utf-8") as f:
        f.read()
參考資料 :
1.stackoverflow

2021年4月7日 星期三

Python - BeautifulSoup 基本應用 (2)

Navigating the tree - Going down

  • child Tag
    前一篇呼叫 Tag 底下的 subTag 是用像 class 的方式 (Tag.subTag),沒完整說明的是這個 subTag 可呼叫到的範圍並不只侷限 children Tag,而是整個 Tag 底下第一個遇到的 Tag.name == subTag。
        """ html
        <b class="boldest" id="bold man">
            <u>Extremely bold</u>
        </b>
        <p>Just a paragraph</p>
        """
        soup = BeautifulSoup(html, 'html.parser')
        print(soup.u)  # <u>Extremely bold</u>
  • find_all()
    依循前面的 subTag 找法,find_all 則會找出所有的 Tag (Tag.name == subTag)。
        soup.find_all('a')
        # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
        #  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
        #  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
  • .contents
    Tag.contents 回傳包含所有 child string 的 list。
        print(soup.contents)  # [<d class="car"><u>Extremely bold.</u></d>, <p>123</p>]
  • .children
    Tag.children 回傳包含所有 child Tag object 的 list。
        for child in soup.children:
            print(child.name)
        # b
        # p
  • .descendants
    回傳一個不只包含 Tag 底下一層的 subTag,而是整個 tree 上所有 object。底下的結果之所以會有 None,是因為 NavigableString 也是 child 的包含範圍 ( .contents 跟 .children 也一樣 ),而 NavigableString object 沒有 .name。
        for child in soup.descendants:
            print(child.name)
        # b
        # u
        # None
        # p
        # None
  • .string
    回傳底下唯一一個的 NavigableString 。如果 Tag 只有一個 child 而且還是 NavigableString 則回傳。如果 Tag 只有一個 child 且 child 也只有一個 NavigableString child 也是回傳。如果有兩個以上的 NavigableString 則要呼叫 .strings,.string 會回傳 None。
        print(soup.u.string) # Extremely bold
        print(soup.b.string) # Extremely bold
        print(soup.string)   # None
  • .strings & .stripped_strings
    strings 會回傳所有 NavigableString。但這種情況下仍會紀錄到一些換行或字串前後的空格,所以可以用 stripped_strings 回傳乾淨的 NavigableString list。(如果整個字串都是空格或換行,則這個 NavigableString 會被無視)
        for sstr in soup.strings:
            print(repr(sstr))
        # 'Extremely bold'
        # 'Just a paragraph' 

Navigating the tree - Going Up

  • .parent & .parents
    基本上就是往上一層的 Tag (Soup 的 parent 為 None)。
        link = soup.a
        link
        # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
        for parent in link.parents:
            print(parent.name)
        # p
        # body
        # html
        # [document]

Navigating the tree - Going sideways

  • .next_sibling &.previous_sibling
    往前找跟往後找,但是要注意的是他沒那方便幫你找到下一個或上一個的 Tag object,絕大部分都是 NavigableString,例如換行 (\n) 之類的。
        tag1 = soup.b
        tag2 = soup.p
        print(tag1.next_sibling)     # <p>123</p>
        print(tag2.previous_sibling) # <b class="boldest" id="bold man"><u>Extremely bold</u></b>
參考資料 :
1.Beautiful Soup 4.9.0 documentation

Popular Posts