Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response. Flask & Angular

angularからflaskにAPIアクセスすると上記のようなヘッダー

 

Failed to load http://localhost:5000/hoge/: Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

 

のようなエラーが発生しました。

理由としてはflask側で OPTIONSのレスポンス時に

Access-Control-Allow-Headers

を返却していないためでした。

 

f:id:shohu33:20170928164701j:plain

flask-cors(1.8)というライブラリ使っていたんですが

 

API Docs — Flask-Cors 3.0.3 documentation

 

を見ると、allow_headersとか、expose_headersとか記載があったんですが、バージョン違うようで(githubのタグ見ると1.8しかない、かつ、pipで入れると1.8なんですけどね、、、) どうもこれらオプションが使えないと。

 

なんで、

github.com

のソースをおっておくと、オプションが headersというのがありました。

これを、flask生成時に以下のように指定してあげると

 

CORS(app, headers='Content-Type')

 

無事、

Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

のエラーが発生しなくなりました。

これはハマりぽいんだな。

macのdocker上でre:dash動かす

結構いろんな日本語のドキュメント出回っていてうまくre:dash立ち上げる事ができなかったのでメモ。

Setting up a Redash instance · Redash Help Center

こちらのドキュメントをみればOK。

以下手順

git clone https://github.com/getredash/redash.git
cd redash
vi docker-compose.production.yml
-----
いい感じに直す。自分は
REDASH_DATE_FORMAT: "YYYY/MM/DD"  <--- 日付フォーマット指定
REDASH_ADDITIONAL_QUERY_RUNNERS: "redash.query_runner.python"  <--- pythonをデータソースに加えれる
を付け加えただけ
-----
docker-compose -f docker-compose.production.yml run --rm server create_db
docker-compose -f docker-compose.production.yml up

このあと、

http://localhost

にアクセスすると

f:id:shohu33:20170601152349j:plain

のような画面が表示される。(本当は、メアドなど最初は登録画面が表示される、、、)
ログインして以下画面が表示されればOK

f:id:shohu33:20170601152434j:plain

PGに簡単なゲームのやり方を学習させる vol1 - まずはQ学習を理解する

社内勉強会用に資料つくったので、slideshareにあげた。

ただ、社内では speakerdeck の方がよいとの声が、、、関係ないけどどっちがよいんだ。

 

www.slideshare.net

Vagrant で 起動時にパスワードを聞かれる場合の対処方法 in Mac

自分のMacの環境でVagrantで起動時に以下のようにパスワードが聞かれてしまいいちいち起動の度にパスワードを入力する必要がありました。

==> default: Preparing to edit /etc/exports. Administrator privileges will be required...
Password:

以下ページで似たような質問していた人いたのでページを見てみると

askubuntu.com

macの場合は以下対応すればよいとのこと。

As of version 1.7.3, the sudoers file in OS X should have these entries: 
Cmnd_Alias VAGRANT_EXPORTS_ADD = /usr/bin/tee -a /etc/exports
Cmnd_Alias VAGRANT_NFSD = /sbin/nfsd restart
Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/ d -ibak /etc/exports
%admin ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE

ようはmac上で visudo を行いファイル最後に追記(As of ... は除く)。 これでめでたくパスワードを聞かれなくなりました。

Google Spreadsheet から DBにデータ保存して参照する

Google Spreadsheet は シートに値を埋めていけば簡易DBになるんですが、多少データ量が増えてくると200万セル制限にひっかかったりして面倒なことになります。

hacknote.jp

このためDBにデータを保存しようとして、いろいろ探してみました。

Google Cloud SQL

f:id:shohu33:20160509174524j:plain

Google Cloud SQL を使えばGoogle Spreadsheetから簡単にMySQLにアクセスできるんですが、お金がかかるということで却下

JDBC  |  Apps Script  |  Google Developers

Fusion Tables

f:id:shohu33:20160509174818j:plain

Fusion Tables というDB(平たくいうとGoogleが提供している地図情報と連携できるデータベースのようなものだそうです)は無料で使えそうとのことで以下ページを参考に試してみました。

support.google.com

Googleドライブでデータベースが使えるという衝撃 | 非IT企業に勤める中年サラリーマンのIT日記

上記サイトでは select, insert は例がのっていましたが、update に関しては記載がなかったので試してみます。

Fusion Tables は上記サイトを参考に使える状態にしておきます。

Fusion Tables に update 文実行

Update する際にどうも ROWID という行番号以外は指定できないようです。 このため事前に以下のようなselect文でROWIDを取得してから

// Utilities.formatString でなぜかサーバーエラーがでるようになるため文字列連結
var sql = "SELECT ROWID FROM "
+ ファイルID + " "
+ "WHERE "
+ "AND PROJECTID = '12345' "
;
var rowid = FusionTables.Query.sql(sql).rows[0];

以下のようなUpdate文で更新する必要があります。

  • これ誰か他に良い方法が他にあれば教えてください。
var sql = "UPDATE "
+ ファイルID + " "
+ "SET value = 1 "
+ "WHERE ROWID = '12345'
;
FusionTables.Query.sql(sql); // UPDATEされるはず

Fusion Tables の1テーブルごとに最大行数ってあるの?

ドキュメントがまだ見つからない、、、

support.google.com

どこかに記載あるのだろうか。

以下記事の情報もガセネタっぽい。

productforums.google.com

Google Script Api で Webサイトスクレイピング

特定サイトページの商品金額を抜き出してgoogle spreadsheetに書き込むために、スクリプトエディタの Google Script Api で Webサイトスクレイピング やることになったのでいろいろ Google Spreadsheetと連携できるGoogle Script Api で Webサイトスクレイピングできる方法をさぐってみた

HTMLの一部分を抜き出せる

Easy data scraping with Google Apps Script in 5 minutes ~ kutil.org

これだと1ページで何箇所か金額部分だけ抜き出すときなど、苦労しそう。

正規表現でごりごりスクレイピング

GoogleAppsScriptでWebスクレイピング

正規表現マスターしてると楽なんでしょうが、DOM操作できるようなライブラリが欲しいな、と。

XML Service Service

googleが提供している XML Parserあるんですが、

XML Service Service  |  Apps Script  |  Google Developers

以下ページを見ると有効なXMLしか扱えないようで、使い勝手が悪いようです。 * 既存のHTMLベージはだいたい有効なXML形式になってないですからね、下記ページでもそのことに触れられています

stackoverflow.com

結局どの方法がいいんだ? -> Xml.parse で解析したデータに、XmlService.parse して解析する

stackoverflowでも取り上げられてました。

stackoverflow.com

Xml.parse で解析したデータに、XmlService.parse して解析すると良さそうとのこと。 Xml.parse で有効なXML形式に変えてくれるんですね。

var page = UrlFetchApp.fetch(contestURL);
var doc = Xml.parse(page, true);
var bodyHtml = doc.html.body.toXmlString();
doc = XmlService.parse(bodyHtml);
var root = doc.getRootElement();

試しに上記で実際にPGうごかしてもたら body が複数配列で取れてしまう場合もあるらしいのでその場合は

var bodyHtml = doc.html.body[1].toXmlString();

などして切り抜ける

さらにDom操作しやすくする

以下のページのソースを拝借するとDom操作しやすくなりますので、こちらも使用するとさらに使い勝手よくなります。これでしばらくいけるんじゃないか説。

yoshiyuki-hirano.hatenablog.jp

サンプル

function sample() {
  
  // dom取得
  var contestURLBase = "https://www.makuake.com/discover/projects/search/";
  var contestURL = contestURLBase + "1";
  var page = UrlFetchApp.fetch(contestURL),
      doc = Xml.parse(page, true),
      bodyHtml = doc.html.body[1].toXmlString(), // なぜかbodyが2つとれるので[1]の方のみ対象
      xmldoc = XmlService.parse(bodyHtml),
      xml   = xmldoc.getRootElement(),
      projects = getElementsByClassName(xml, 'projectBox');
  
  for (var i = 0; i < projects.length; i++) { 
    var project = projects[i];
    // タイトル取得
    var item = getElementsByTagName(project, 'h2');
      Logger.log(item[0].getValue()); 
  }
  
}

所有しているサイトの検索キーワード数を取得してGoogle Spreadsheetに値を記載する

はじめに

所有しているサイトが特定のキーワードでどれくらい検索されているかGoogle Search Consoleで調べてGoogle Spreadsheetに記載して日々計測する必要があったため、その際に行った手順を残しておきます。

f:id:shohu33:20160503135239j:plain

検索回数や表示回数を取得できる Google Search Console

Google Search Console(以前は Webmaster tool だった気がします)という以下のURLで、自分が所有しているサイトはどのようなキーワードで検索されたのか?また、そのキーワードで何回表示されて何回クリックされたなどの値が取れます。

www.google.com

以下のような感じで確認できます。

f:id:shohu33:20160503134402j:plain

こちらの値をなんとか Google Spreadsheetの方に引っ張ってきて、毎日記録させようと思います。

苦戦しながら Google OAuth2 でリフレッシュトークンなどを取得する

Google Search Console の値を取得するために、GoogleDevelopers の Search Console API(Webmaster Tools API)を使用します。

Search Analytics: query  |  Search Console API (Webmaster Tools API)  |  Google Developers

さらにAPIを使用するために、Google OAuthを利用する必要があるのですが、これがややこしい手順を踏む必要があるります。

ただ、そのややこしい手順をまとめてくれているサイトがあるので、以下ページを参考に リフレッシュトークンというやつを取ります。

qiita.com

端的にいうと、このリフレッシュトークンがないと長い間(たとえば3ヶ月など)、バッチなどでPGで自動実行してGoogle Search Consoleの値を取得することができなくなってしまいます。

詳しいトークンの説明は以下に譲ります

murashun.jp

上記ページに細くすると以下点に注意が必要です

それではGoogle Search Consoleの値にアクセスして値を取得してみます

Googleにログインして、Google Spreasheet で新しいドキュメントファイルを作成します。

f:id:shohu33:20160503135041j:plain

[ツール] - [スクリプトエディタ] を選択して、スクリプトエディタを開きます

f:id:shohu33:20160503140600j:plain

以下のソースベースにsite_urlなどをカスタマイズして実行すると

function myFunction() {
    var opt = {
        "method" : "POST",
        "payload" : {
            "client_id" : GS_CLIENT_ID, // 取得した クライアントID
            "client_secret" : GS_CLIENT_SECRET, // 取得した クライアントシークレット
            "refresh_token" : GS_OAUTH_REFRESH_TOKEN, // 取得したリフレッシュトークン
            "grant_type" : "refresh_token"
        },
        // "muteHttpExceptions" : true
    };
    var res = UrlFetchApp.fetch("https://accounts.google.com/o/oauth2/token", opt);
    var dat = JSON.parse(res.getContentText());
    var accessToken = dat["access_token"];
  
    var filters = [];
    filters.push({
        dimension: "query",
        operator: "contains",
        expression: "酒"
    })

    var params = {
      searchType: "web",
      startDate: "2016-04-17",
      endDate: "2016-04-17",
      dimensions: ["query"],
      dimensionFilterGroups:[
        {
          filters: filters
        },
      ],
    }
        
    var opt = {
      "method" : "POST",
      "payload" :JSON.stringify(params),
      "contentType": "application/json",
      "headers" : {
          "Authorization": "Bearer " + accessToken,
      },
    };
    var site_url = "http%3A%2F%2Fexmaple.com%2F";
    var res = UrlFetchApp.fetch("https://www.googleapis.com/webmasters/v3/sites/"+ site_url +"/searchAnalytics/query", opt);
    var content = JSON.parse(res.getContentText());
    Logger.log(content);
}

ログに以下のようなjson文字列が取得することができます。

{
 "rows": [
  {
   "keys": [
    "果実酒 飲み放題"
   ],
   "clicks": 5.0,
   "impressions": 26.0,
   "ctr": 0.19230769230769232,
   "position": 4.8076923076923075
  },

最後に取得した値をGoogle Spreadsheetに記載します。 先ほどのjsonの文字列からJSON.parseなどで必要な値を取得して以下ページを参考に必要な箇所に記載していきます。

[GAS][スプレッドシート]シートに値をセットするには : 逆引きGoogle Apps Script

スクリプトを自動化する

リフレッシュトークンを取得しているので、何度も認証する必要がないため、あとは定期的にトリガーで上記のPGを実行できるようにすれば、毎日値をGoogle Spreadsheetに追記していくことが可能です。

f:id:shohu33:20160503150947j:plain f:id:shohu33:20160503150952j:plain