2014年9月11日 星期四

iOS 開發筆記 - 透過 iTunesConnect RSS 處理 App Customer Reviews 資料

以 Facebook 而言,可以輕易得知 App ID 為 284882215,而 iTunesConnect 有提供 RSS 方式查詢 Customer Reviews:

https://itunes.apple.com/us/rss/customerreviews/id=284882215/sortBy=mostRecent/xml

其中,上述有三個主要可變動的參數:country(us), app id (284882215), format(xml)

最近比較愛 json 的:

https://itunes.apple.com/us/rss/customerreviews/id=284882215/sortBy=mostRecent/json

想要台灣區評價:

https://itunes.apple.com/tw/rss/customerreviews/id=284882215/sortBy=mostRecent/json

問題應該就是 country 到底有哪些,可以用既定資料每個都抓一遍啦。

對於格式有興趣的可以用得到可讀性較佳的資料格式:

$ curl https://itunes.apple.com/tw/rss/customerreviews/id=284882215/sortBy=mostRecent/json | python -mjson.tool

若仔細看的話,還可以看到 User ID 、追蹤到 User 對其他款 app 評價等,此外,也有 first page 跟 last page 存取方式:

https://itunes.apple.com/tw/rss/customerreviews/page=1/id=284882215/sortby=mostrecent/json
https://itunes.apple.com/tw/rss/customerreviews/page=10/id=284882215/sortby=mostrecent/json

最大頁數只有到 10 而已(CustomerReviews RSS page depth is limited to 10)

至於要用 json 還是 xml 好?目前的心得是... json 會缺少 user comment updated 資訊,而 xml 卻也會碰到 format error 的情況 Orz 只能...看著辦了 XD


以 PHP 處理為例:

$ cat test.php
<?php
$app_id = '284882215';
$country_list = array( 'tw', 'us');
$format = 'xml';
$page_list = array(1,2,3,4,5,6,7,8,9,10);
foreach( $country_list as $country ) {
        foreach( $page_list as $page ) {
                $url = "https://itunes.apple.com/$country/rss/customerreviews/id=$app_id/page=$page/$format";
                if ($format == 'json') {
                        $raw = json_decode(@file_get_contents($url), true);
                        //print_r($raw);
                        for ($i=1, $cnt=count($raw['feed']['entry']) ; $i<$cnt ; ++$i) {
                                print_r(array(
                                        'user_id' => $raw['feed']['entry'][$i]['id']['label'],
                                        'user_name' => $raw['feed']['entry'][$i]['author']['name']['label'],
                                        'user_uri' => $raw['feed']['entry'][$i]['author']['uri']['label'],
                                        'title' => $raw['feed']['entry'][$i]['title']['label'],
                                        'content' => $raw['feed']['entry'][$i]['content']['label'],
                                        'version' => $raw['feed']['entry'][$i]['im:version']['label'],
                                        'rating' => $raw['feed']['entry'][$i]['im:rating']['label'],
                                ));
                        }
                } else {
                        $raw = simplexml_load_string(@file_get_contents($url));
                        //print_r($raw);
                        for($i=1, $cnt = count($raw->entry); $i<$cnt ; ++$i) {
                                //print_r($raw->entry[$i]);
                                $imAttrs = $raw->entry[$i]->children('im', true);
                                //print_r($imAttrs);
                                print_r(array(
                                        'date' => (string)$raw->entry[$i]->updated,
                                        'user_id' => (string)$raw->entry[$i]->id,
                                        'user_name' => (string)$raw->entry[$i]->author->name,
                                        'user_uri' => (string)$raw->entry[$i]->author->uri,
                                        'title' => (string)$raw->entry[$i]->title,
                                        'content' => (string)$raw->entry[$i]->content[0],
                                        'version' => (string)$imAttrs->version,
                                        'rate' => (string)$imAttrs->rating,
                                ));
                        }
                }
                exit;
        }
}

$ php test.php
Array
(
    [date] => 2014-09-10T09:10:00-07:00
    [user_id] => ###########
    [user_name] => ###########
    [user_uri] => https://itunes.apple.com/tw/reviews/id###########
    [title] => 有Bug請改善
    [content] => 點朋友的動態跑不出來!!
    [version] => 14.0
    [rating] => 1
)
Array
(
    [date] => 2014-09-10T09:03:00-07:00
    [user_id] => ###########
    [user_name] => ###########
    [user_uri] => https://itunes.apple.com/tw/reviews/id###########
    [title] => 一直自動關掉
    [content] => 用一用會一直自動跳掉,很頻繁,很煩。
    [version] => 14.0
    [rating] => 1

)

2014年9月10日 星期三

[SQL] SELECT IN SELECT 以及 Pagination 的使用 @ MySQL 5.6

使用 SQL 語法時,有時會需要從另一張 Table 取出清單,接著對清單內的資料做為基準再進行一次資料的擷取,直觀的想法大概是 SELECT something FROM Table1 WHERE id IN (SELECT id FROM Table2 WHERE ... )。

可惜上述語法是不行的 XD 要改成 JOIN 的做法:

SELECT something FROM Table1, (SELECT id FROM Table2) AS list WHERE Table1.id = list.id;

接著,偶爾會需要 pagination 的需求,加個 LIMIT 的用法,這時候又會想要回報全部有幾筆資料(對於一些搜尋引擎的設計,有些是採用預估的方式),以便前端可以估算有幾筆資料。

最簡單的解法是再用一個 SQL Query 去問 Table2 的 id 資料,但想要更快一點,就來試試 MySQL User-Defined Variables 吧!

SELECT something, @n AS total FROM Table1, (SELECT id, CASE WHEN @n > 0 THEN @n := @n + 1 ELSE @n := 1 END AS n FROM Table2, (SELECT @n := 0) AS init) AS list WHERE Table1.id = list.id;

如此一來,結果都會有個 total 筆數跟著,雖然仍不夠好,但也不錯啦 XD  而搭配 LIMIT OFFSET,COUNT 時,total 的資訊是來自掃 Table2 的資料,所以也能正常顯示:

SELECT something, @n AS total FROM Table1, (SELECT id, CASE WHEN @n > 0 THEN @n := @n + 1 ELSE @n := 1 END AS n FROM Table2, (SELECT @n := 0) AS init) AS list WHERE Table1.id = list.id LIMIT 0,10;

[C++] 使用 PCRE、RE2 進行 Match all (如同 PHP preg_match_all 效果) @ Ubuntu 14.04

對 PHP 來說:

$ vim t.php
<?php
$data = "..<a href='...'>...</a>.."; //file_get_contents(...);
if (preg_match_all("#<a[^h]*href=['\"]{0,1}([^\"']+)[\"']{0,1}[^>]*>(.*?)</a>#", $data, $matches) )
        print_r($matches);
$ php t.php
Array
(
    [0] => Array
        (
            [0] => <a href='...'>...</a>
        )

    [1] => Array
        (
            [0] => ...
        )

    [2] => Array
        (
            [0] => ...
        )

)


對 PCRE 來說:

$ vim pcre_test.cpp
#include <pcre.h>
#include <iostream>

int main() {

const char *error;
int erroroffset;
pcre *preg_pattern_a_tag = pcre_compile("<a[^h]*href=['\"]{0,1}([^\"']+)[\"']{0,1}[^>]*>(.*?)</a>", PCRE_MULTILINE, &error,  &erroroffset, NULL);

if (!preg_pattern_a_tag) {
std::cout << "ERROR\n";
return -1;
}

std::string raw = "..<a href='...'>...</a>..";

unsigned int offset = 0;
unsigned int len = raw.size();
int matchInfo[3*2] = {0};
int rc = 0;

while (offset < len && (rc = pcre_exec(preg_pattern_a_tag, 0, raw.c_str(), len, offset, 0,  matchInfo, sizeof(matchInfo))) >= 0) {
for (int n=0; n<rc ; ++n) {
int data_length = matchInfo[2*n+1] - matchInfo[2*n];
std::cout << "Found:[" << raw.substr(matchInfo[2*n], data_length) << "]\n";
}
offset = matchInfo[1];
}
return 0;
}
$ g++ -std=c++11 pcre_test.cpp -lpcre
$ ./a.out
Found:[<a href='...'>...</a>]
Found:[...]
Found:[...]


對 RE2 來說:

$ vim re2_test.cpp
#include <re2/re2.h>
#include <iostream>

int main() {

//RE2 preg_pattern_a_tag("<a[^h]*href=['\"]{0,1}([^\"']+)[\"']{0,1}[^>]*>(.*?)</a>", RE2::Latin1);
RE2 preg_pattern_a_tag("<a[^h]*href=['\"]{0,1}([^\"']+)[\"']{0,1}[^>]*>(.*?)</a>");

std::string raw = "..<a href='...'>...</a>..";

re2::StringPiece result_a_href, result_a_body;

while(RE2::PartialMatch(raw, preg_pattern_a_tag, &result_a_href, &result_a_body)) {
std::cout << "result_a_href:[" << result_a_href << "]\n";
std::cout << "result_a_body:[" << result_a_body << "]\n";
raw = result_a_body.data();
}
return 0;
}
$ g++ -std=c++11 re2_test.cpp /path/libre2.a -lpthread
$ ./a.out
result_a_href:[...]
result_a_body:[...]

2014年9月3日 星期三

AWS 筆記 - 使用 Amazon Route53 設定 DNS SPF Record

如果說 MX 是用來提供寄件者得知 mail server 在哪邊,那 SPF 則是提供 mail receiver 驗證寄件者使用的 mail server 是不是接近已知、合法的 server。因此,設定 DNS SPF Record 是會降低信件誤判成垃圾郵件的機率。

採用 Amazon Route53 時,設定 TXT Record (有 SPF Record,但這次沒試)

"v=spf1 a mx a:YourServerHostname  ~all"

最後面的 ~all 代表上述未定義的,可能是錯的。若要強制歸類在錯誤,就用 -all 即可。

驗證方式(此例以 cs.nctu.edu.tw 為例):

$ nslookup -q=txt cs.nctu.edu.tw
...
cs.nctu.edu.tw  text = "v=spf1 a mx a:farewell.cs.nctu.edu.tw a:csmailer.cs.nctu.edu.tw a:tcsmailer.cs.nctu.edu.tw a:csmailgate.cs.nctu.edu.tw a:csmail1.cs.nctu.edu.tw a:csmail2.cs.nctu.edu.tw a:csws1.cs.nctu.edu.tw a:csws2.cs.nctu.edu.tw ~all"
...


可以看到 "v=spf1 ... " 資訊,代表 DNS TXT Record 設定無誤,記得這是有 dns cache 機制,不見得馬上更改或是馬上可以查詢到。

接著,可以用 Gmail 來測試,從自家 mail server 寄信到 Gmail,若沒有設定 DNS SPF Record ,會顯示:

Received-SPF: none (google.com: YourSender@YourMailServer does not designate permitted sender hosts) client-ip=YourMailServerIP;

若有設定 DNS SPF Record 但不在名單內,此例為 ~all 用法:

Received-SPF: softfail (google.com: domain of transitioning YourSender@YourMailServer does not designate YourMailServerIP as permitted sender) client-ip=YourMailServerIP;

若設定正確即在 DNS SPF Record 定義內:

Received-SPF: pass (google.com: domain of YourSender@YourMailServer designates YourMailServerIP as permitted sender) client-ip=YourMailServerIP;

要留意,如果租的機器是在一些常見的 VPS 時,對外走的可能是 IPv6 ,此時記得 DNS Reocrd 中,也要用 AAAA Record (IPv6) 定義。

2014年9月2日 星期二

[Linux] 透過 alias 與 virtual 機制建立 no-reply @ hostname 帳號 @ Postfix 2.9.6, Ubuntu 12.04

新增一筆 alias 用來導向 /dev/null

$ grep alias_maps /etc/postfix/main.cf
alias_maps = hash:/etc/aliase
$ sudo vim /etc/aliases
...
devnull: /dev/null
...


採用 virtual account 新增 no-reply 帳號:

$ grep virtual_alias_maps /etc/postfix/main.cf
$ sudo vim /etc/postfix/main.cf
virtual_alias_maps = hash:/etc/postfix/virtual

$ sudo vim /etc/postfix/virtual
no-reply@hostname devnull

$ sudo newaliases
$ sudo postmap /etc/postfix/virtual
$ sudo postfix reload
postfix/postfix-script: refreshing the Postfix mail system


至於測試方式,就直接連他寄信看看:

$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 hostname ESMTP Postfix (Ubuntu)
HELO localhost
250 hostname
mail from:<root@localhost>
250 2.1.0 Ok
rcpt to:<no-reply@hostname>
250 2.1.0 Ok
quit
221 2.0.0 Bye
Connection closed by foreign host.


如果出現 451 4.3.0 <no-reply@hostname>: Temporary lookup failure ,請記得翻一下 /var/log/mail.err ,若是單純 error: open database /etc/postfix/virtual.db: No such file or directory 問題,記得跑一下 sudo postmap /etc/postfix/virtual 即可。