2013年4月15日 星期一

scrapy 快速上手



Scrapy中各個模組切得很乾淨,該分的都已經分好,只要了解各模組的運作流程就能快速上手。


  • Overview

     Project -> spiders -> myspider.py =>爬網站邏輯, 取欄位or發Request爬下一層
                -> items.py => 定義在Scrapy中傳遞的物件field
                -> pipeline.py => 儲存DB的邏輯
                -> middlewares.py  => 各個Scrapy模組間的橋梁(請參考附圖) 分 Downloader, Spider, Scheduler
                -> settings.py => 各種設定參數 https://scrapy.readthedocs.org/en/latest/topics/settings.html



  • 常用Console指令

startproject : 開project
shell : 試爬一個網址(寫Spider主程式邏輯時很需要)
crawl : 執行一個完整的spider
list : 列出目前project的spider
deploy : 佈署相關


  • Spider

    Scrapy中的Spider有很多種:BaseSpider, CrawSpider, CSVFeedSpider,...,依照網站複雜度跟特性使用對應的Spider

    爬黃頁只需要用BaseSpider
    class MySpider(BaseSpider):
    name                          : Spider名稱
    allowed_domains[]             : 如其名
    start_urls[]                  : 裡頭可以裝一串要爬的網址
    start_requests()              : 可以取代start_urls用iterable物件產生url (高級物,沒特別不需覆寫)
    make_requests_from_url(url)        : 會取start_urls裡的url送成Request物件 (高級物,沒特別不需覆寫)
    parse(response)                             : BaseSpider預設的callback function,回傳Response物件
  https://scrapy.readthedocs.org/en/latest/topics/request-response.html

  主要邏輯寫在Request定義的callback function裡面(預設是parse)
  return an iterable of Request (繼續爬下一頁) and/or Item (丟給pipeline儲存) objects.


  • hxs

    高級物,請自學,
    可用  scrapy shell http://網址.com  來測試
     

  • Log

  from scrapy import log
  log.msg("Hellow World!", level=log.ERROR)

  level:
CRITICAL - for critical errors
ERROR - for regular errors
WARNING - for warning messages
INFO - for informational messages
DEBUG - for debugging messages
    在setting.py設定觀看等級



  • Middleware

  Middleware有千奇百萬種,主要用途在各模組間加料。ex:Proxy,Cookie,Login https://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html?highlight=Middleware

  ex:
  DownloadMiddleware
    process_request(request, spider)                       : request時
    process_response(request, response, spider)      : response時
    process_exception(request, exception, spider)    : 發生exception時



  • Deploy

  主要好處由scrapyd幫你管理spider,可以發簡單的curl就能控制監控執行中的spider。

  環境設置
  https://scrapy.readthedocs.org/en/latest/topics/scrapyd.html?highlight=deploy
 
  常用指令
    scrapy deploy scrapyd -p yourproject
    curl http://localhost:6800/schedule.json -d project=yourproject -d spider=spidername
    curl http://localhost:6800/listjobs.json?project=yourproject
    curl http://localhost:6800/cancel.json -d project=yourproject -d job=jobid

2013年2月3日 星期日

Map Reduce in MongoDB

1. 在程式內呼叫 mongodb 內建之 map reduce 的方法
 
from bson.code import Code 
# main logic
log_collection = getReadCollection('LOG')
mapper = Code(mapper_code)
reducer = Code(reducer_code)
output_collection = getReadCollection('PAGEVIEW')
results = hfd_collection.map_reduce(mapper, reducer, output_collection)
 
ps: mapper_code 及 reducer_code 是由 javascript 作為語言的程式碼片段字串, 例如
 
# mapper_code
function() {
 if (this.country=="US"){ 
         emit(this.number , {freq: this.freq})
  }
}
 
# reducer_code 
function reduce(key, values) {
     var total = 0;
     for (var i=0;  i< values.length ; i++ ) {
          total += values[i].freq;
     }
     return {freq: total}
}
 
2. 注意事項
 
最後產生的結果會如下格式:
{
   "_id" : key,
   "value" : { value }
}
 
而 key 即是 mapper_code 中 emit 函式的第一個參數 , 上例中最後運算的結果格式如下
{
  "_id" : u223686999,
  "value" : { "freq" : 30 }
}
 
然而若要讓 map reduce 以多個 key 為條件去做 aggregation ( google 關鍵字可下 : compound key + mongodb ) , 則可修正如下:
 
# mapper_code
function() {
 if (this.country=="US"){ 
         emit( { number: this.number ,date: this.date } , {freq: this.freq})
  }
}
則會產生
{
  "_id" : { 
     "number" : u862236699,
     "date" : ISODate("2013-01-31T00:00:00.0Z")
   }
  "value" : { "freq" : 30 }
}
 
Reference : http://cookbook.mongodb.org/patterns/unique_items_map_reduce/

2012年9月30日 星期日

HTML 內文擷取

各式各樣網頁文字內容的掘取是做各種後續處理 (例如撰寫 search engine 的 crawler) , 分析的第一步, 一般人想到掘取一個網頁內容最直覺的想法就是發送 HTTP GET, 再透過一些 meta tag , DOM processing library 來將回傳的 html 中之標籤移除 (透過 beautifulsoup 之類的 html cleaning tool); 然而, 在近代的網頁中, 例如論壇或部落格, 真正重要/有意義的內容往往只佔整個頁面的一小部分 (只保留 user generate article, 而不是廣告 , 留言, social widget,  tags , button 等與主要文章不相關的 text)

另外, 除了便於分析研究等相關工作外, 網頁內容掘取的技術也被 apply 到許多應用上 , 例如可以將網頁文字內容擷取並美觀的應用 Readability , 可以讓使用者將有興趣閱讀的網頁內容 "稍後再看" 的 pocket app ; rss/news reader app 如 pulse , feedly , 甚至是為了讓現有網頁能夠在 mobile 端被適當的呈現給使用者

網路上有人整理了幾種目前解決 html content extraction 演算法的比較 : http://tomazkovacic.com/blog/56/list-of-resources-article-text-extraction-from-html-documents/ 裡頭整理了目前學界在此問題上的 state-of-arts solutions.

而對於開發者來說, 其實大可不必要花太多時間研讀上述所提及的演算法 ; 開發出 readability 的 arc90 公司除了提供 API 之外也將其 source code 開放源碼, 目前更陸續被 port 在許多不同的語言 , 例如 php , ruby 與 python , java 等等版本 , 讓使用者可以自行選擇適合的

除了 readability 的 solution 外, 以下整理出一些其他的 solutions :

Google mobilizer : http://www.google.com/gwt/n
Pocket API 提供的 parser : http://getpocket.com/api/
fivefilters  : http://fivefilters.org/content-only/
viewtext : http://viewtext.org/
Goose : http://jimplush.com/blog/goose





2012年8月29日 星期三

redis in python



Redis 為 Remote Dictionary Server 的縮寫, 是一款 In-Memory 的 NoSQL 資料庫, 因為是 In-Memory DB , 在資料的 access 上會比其他任何形式的資料庫都來的快速, 但是在規劃 data model 時必須注意資料的容量受限於實體記憶體的大小 ; 另外值得一提的是, redis 相對於其他的 NoSQL database , 提供了更為多元的 high-level data structure , 讓開發者在規劃 與開發時更為彈性與便捷


(1) 安裝 redis database

在 http://redis.io/download 中可以選擇欲使用的版本, 網頁下方也清楚展示了安裝的方式 ; redis-cli 是一個互動式的操作環境, 可以在此程序中直接輸入指令來操作資料庫

(2) 安裝 redis python client

由於 redis 的便利性, 使其廣為各個語言所支援 , 目前多數主流程式語言的 redis client 皆已 被開發完成 ; 而在 python 中, 目前多用 redis-py 來開發

只要在 .py script 檔中 import redis 即可再該檔案內操作 redis database
redis-py 進一步使用方式可以參考 http://degizmo.com/2010/03/22/getting-started-redis-and-python/

(3) 注意事項

以下列出一些 redis 原生指令與在 redis-py 中幾個差異處

a. select db : 在 redis-cli 環境中可以透過 select n 來切換目前的 key-space ( 例如可以用來區分 testing 與 deploy 的 db 版本 ) , 然而在 python 中只能透過一開始在初始 redis 物件時來選擇 key space , 例如 r = redis.Redis("localhost",db=1) 即為選擇第一號 data space

b. 刪除某個 key : 在 redis-cli 中欲刪除某個 key 的指令為 del key_name , 然而 del 在 python script 中是被視作保留字 (reserved word) , 因此在 redis-py 中要使用 r.delete(key_name)


2012年4月28日 星期六

在 ec2 (ubuntu)上使用 web.py 透過 nginx 與 fastcgi 佈署 web application


最近要在 amazon ec2 (ubuntu 11) 上使用 python + Redis 開發一套簡單的 api , 因此選擇了一套 lightweight 的 http framework 來 handle REST http requests , 因為也是初學 python , 因此首先選擇嘗試較輕量化的 web.py 而不是大多數人使用的 django 

在 web.py 的官方網站 中提到了三種主要 deployment 的方式 , 分別是透過 apache , lighttpd 與 nginx , 分別試過這三種途徑以及自身需求後[1] , 覺得透過 nginx + fastcgi 的方式算是相對簡潔與高效的作法 , 在此簡單記錄步驟並且補充官網上沒提到的一些值得注意的細節

step 1: 下載必要的套件

sudo apt-get install python-flup
sudo apt-get install nginx
sudo apt-get install fast-cgi

step 2: 創建佈署 app 時對應的 server 設定檔

將原始設定檔拷貝一份, 並且在 HTTP { /.../ } 中加入以下 

location / {
        fastcgi_param REQUEST_METHOD $request_method;
        fastcgi_param QUERY_STRING $query_string;
        fastcgi_param CONTENT_TYPE $content_type;
        fastcgi_param CONTENT_LENGTH $content_length;
        fastcgi_param GATEWAY_INTERFACE CGI/1.1;
        fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
        fastcgi_param REMOTE_ADDR $remote_addr;
        fastcgi_param REMOTE_PORT $remote_port;
        fastcgi_param SERVER_ADDR $server_addr;
        fastcgi_param SERVER_PORT $server_port;
        fastcgi_param SERVER_NAME $server_name;
        fastcgi_param SERVER_PROTOCOL $server_protocol;
        fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_script_name;
        fastcgi_pass 127.0.0.1:9002;
}


step 3 : 修改 python script 加入 fast-cgi 的接口

例如:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import web

urls = ("/.*", "hello")
app = web.application(urls, globals())

class hello:
    def GET(self):
        return 'Hello, world!'

if __name__ == "__main__":
    web.wsgi.runwsgi = lambda func, addr=None: web.wsgi.runfcgi(func, addr)
    app.run()


主要是在 app.run() 前加入 web.wsgi.runwsgi = lambda func, addr=None: web.wsgi.runfcgi(func, addr) 


修改前可以先試試 #python your_script.py , 沒特別指定 port 的話預設是跑在 port 8080 , 看 script 是否有誤 , 這裡常會碰到的問題是 8080 被佔住的問題 , 如果被佔住用指令檢查一下並且 kill 掉該 process 即可

step 4 : start & stop script

假設我們的 web.py 主程式名為 code.py , 並且放在 /home/ubuntu/www 下
則 :

# sudo spawn-fcgi -d /hom/ubuntu/www -f /home/ubuntu/www/code.py -a 127.0.0.1 -p 9002

如果成功的話 , fcgi 會給出一個 pid , 之後關閉要透過這個 pid , 或者 # pgrep -f "python /home/ubuntu/www/code.py" 找回 pid , 並且透過 sudo kill -9 pid 來關閉 fcgi

常見的怪問題都是發生在這個步驟中, 例如 502 bad gateway 等, 一開始會以為是 nginx 的問題 , 但經過自己的經驗, 502 一般都是 fast-cgi 沒有成功啟動所造成 (即使如此 , 系統還是有可能會給出成功啟動的訊息! ), 可以在 spawn-fcgi 的指令中 , 加入 -n 參數來看詳細的資訊 或者是在執行指令後, 透過 sudo nentstat -antup 來看是否有成功 spawn fcgi process


step 5 : start & stop nginx service

使用 # sudo nginx -c /home/ubuntu/www/my_nginx.conf 來啟動 , 並且可以透過 sudo service nginx stop 來停止


[1] 由於會搭配 redis 作為後台資料庫 ,在記憶體的資源取捨下放棄 apache 這個較為廣為使用的但較吃記憶體的套件

2012年3月27日 星期二

關於 Hidden Markov Model 的一些整理


  • Markov Property: 如果一個過程(process)的"將來"僅依賴於"現在"而不依賴過去, 則這個過程具有Markov Property, 或稱此過程為馬科夫過程, 即 X(t+1) = f(X(t))
  • Markov Chain: 時間和狀態都離散的馬科夫過程稱為 Markov Chain
  • Hidden Markov Model 與 Markov Model 的差別: 除了 State 之間的 transition, 每個 State 還有自己對於各 Observable symbol 的產生機率
  • "Hidden" of Hidden Markov Model: 在大部分的情況下, 我們無法直接得知真正的"狀態"是什麼, 能夠直接觀測到的往往是一些"表徵", 例如頻率的高低
  • HMM 能夠解決的三個問題
    • (1) 給定一經觀察得到的 sequence 及 model , 評估此序列發生的可能性
      • 即 P(觀察到的現象|模型)     
      • Solution : Forward Algorithm
    • (2) 給定觀察序列以及 model , 找出一個最合理能夠解釋觀察率列的 State 序列    
      • 即 P(狀態序列|觀察到的現象,模型)    
      • Solution : Viterbi Algorithm
    • (3) 經由觀察序列學習出模型的參數    
      • 即 怎樣的 model 可以使 P(觀察到的現象|模型)最大化    
      • Solution : Baum-Welch Algorithm

2012年3月11日 星期日

存取 android market 資料的方法 (二) 第一次爬 Android App Market 就上手 ! Head first crawling Android app market


廢話不多說, 照做以下步驟就可以把整個 market 上的 app 資料都般回家!

開始!


1. 前置準備 

    1.1 一台裝有 Ubuntu 的電腦 

   1.2 並準備好 Ruby on Rails 的環境
          ps. 可參考 http://blog.longwin.com.tw/2008/11/ruby-on-rails-linux-environment-build-2008/ 或網路上任何相關文章 , 此文章是用 Ubuntu 10.04 版

    1.3 安裝 hg
           $sudo add-apt-repository ppa:tortoisehg-ppa/releases
           $sudo add-apt-repository ppa:mercurial-ppa/releases
           $sudo apt-get update
           $sudo apt-get install mercurial python-nautilus tortoisehg

     1.4 下載 source code
           $hg clone https://code.google.com/p/android-marketplace-crawler
           裡頭主要有兩個部分 , 分別是 crawler 以及 server           

2. 準備 crawler 
 
     2.0 安裝 ANT $sudo apt-get install ant
  
     2.1 準備 market-id : 最簡單的方式是在手機上撥打 *#*#8255#*#* 
           會出現如圖畫面 



           請記住紅線部分的字!


     3.1 填入 /crawler/src/com/marketplace/io/Secure.java 中 Secure() , getUsername() , getPassword() 中填妥你的 google 帳號 , 例如 : preferences.put("username_key","你的google帳號");   

     3.2  修改 /crawler/src/com/marketplace/util/Device.java. 中 Device() 的 market id 例如 this.marketId = "你的marketId";

     3.3  修改 /crawler/build.xml 第 38 行
               <fileset dir="/home/你的帳號/android-marketplace-crawler/crawler/bin"/>

     3.3 編譯程式 : $ant          

     3.4 $mv /crawler/permission /path_to_jar_file
           一般情形下 /path_to_jar_file 會在 /crawler/dist/lib 下

4. 準備 server

     4.0 修改 /android-marketplace-crawler/server/Gemfile 如下
           gem 'rake' , '>= 0.9.2' 
           gem 'mysql2', '0.2.18'
           存檔後 , $bundle update
     
    4.1 修改 /config/database.yaml
          增加 socket: /var/run/mysqld/mysqld.sock
          填上 root 之 密碼欄位 
          存檔後 $bundle check,  $bundle install

    4.2 建立資料庫 
          $rake db:create
          $rake db:seed
          $rake db:migrate

    4.3 啟動 Rails server     
          $rails s

5. 開始 crawling! 把資料都搬回家!
    
    切換到剛編譯完的.jar檔所在目錄 
    $java -jar crawler.jar -c


最後, 可以透過瀏覽 http://你的伺服器IP位置:3000 瀏覽目前已經被你爬下來的app資訊!