「ローカル環境でスクレイピングしたデータをBigQueryへ送る」定期実行で出くわした環境面の問題

定期的にローカル環境からスクレイピングしたデータをBigQueryへ送信します。
このプログラムの実装時に考えなければいけなかった環境面の問題をメモします。

- ローカル環境 vs クラウド環境
- 接続エラーが出る
- 環境変数がリセットされる

いったん上の項目だけ作成したものの、本記事の内容は今後作業・勉強を経てアップデート予定。

# ローカル環境 vs クラウド環境
##前提
クラウド環境のほうが管理しやすいのですが、ローカル環境で実装しました。
何種類かの閲覧パターン=cookieに基づいて表示される広告の情報が欲しかったからです。
Cookieはもともとクライアント(ブラウザ)側で持つものなので、
ローカル環境でプログラムを動かしたほうが良いと判断しました。
##対処した内容
**Chromeのユーザープロファイル** 情報を使い、プロファイル単位でcookieを丸ごと利用できるようにしました。

##課題
今後はこれをクラウドで完結できるようにしたい。できるのかな、、

2019/12/27追記：↓ 足がかりとしてこんなことを調べてみました↓ 
[【Google広告】オーディエンスリスト・SSPへのデータ送信内容の検証ノート(Cookie/HTTPリクエスト)](https://qiita.com/MasatoEmata/items/c6c100d56840f174dc66)



# 接続エラーが出る
##問題
BigQueryで一回目はデータを送ることができても、２回目のループで以下のエラーが出ました。
一旦[Googleストレージにアップロードすることで回避できるか検証した](https://qiita.com/MasatoEmata/items/a88305f518e20a1d5e60)が、やはり発生しました。

```
ConnectionResetError: [WinError 10054] 既存の接続はリモート ホストに強制的に切断されました。
```
調べた記事
https://codeday.me/jp/qa/20190418/650536.html
https://stackoverflow.com/questions/34635787/bigquery-job-connexion-closed
http://100mds.blogspot.com/2013/05/wsaeconnreset-10054.html 


▽以下は15分以内の短いループで実行したときに発生

```main.py
def job():
  for i in (スクレイピングする種類の一覧):
　  for x in range(スクロール&データ取得のループ回数):
    UploadCSVtoBigquery(...)

________________________________________________________________
ログ

upload to BigQuery
Traceback (most recent call last):
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 672, in urlopen
    chunked=chunked,
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 421, in _make_request
    six.raise_from(e, None)
  File "<string>", line 3, in raise_from
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 416, in _make_request
    httplib_response = conn.getresponse()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 1336, in getresponse
    response.begin()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 306, in begin
    version, status, reason = self._read_status()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 267, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "C:\Users\masato\Documents\python\lib\socket.py", line 589, in readinto
    return self._sock.recv_into(b)
  File "C:\Users\masato\Documents\python\lib\ssl.py", line 1071, in recv_into
    return self.read(nbytes, buffer)
  File "C:\Users\masato\Documents\python\lib\ssl.py", line 929, in read
    return self._sslobj.read(len, buffer)
ConnectionResetError: [WinError 10054] 既存の接続はリモート ホストに強制的に切断されました。

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\masato\Documents\python\lib\site-packages\requests\adapters.py", line 449, in send
    timeout=timeout
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 720, in urlopen
    method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\util\retry.py", line 400, in increment
    raise six.reraise(type(error), error, _stacktrace)
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\packages\six.py", line 734, in reraise
    raise value.with_traceback(tb)
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 672, in urlopen
    chunked=chunked,
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 421, in _make_request
    six.raise_from(e, None)
  File "<string>", line 3, in raise_from
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 416, in _make_request
    httplib_response = conn.getresponse()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 1336, in getresponse
    response.begin()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 306, in begin
    version, status, reason = self._read_status()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 267, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "C:\Users\masato\Documents\python\lib\socket.py", line 589, in readinto
    return self._sock.recv_into(b)
  File "C:\Users\masato\Documents\python\lib\ssl.py", line 1071, in recv_into
    return self.read(nbytes, buffer)
  File "C:\Users\masato\Documents\python\lib\ssl.py", line 929, in read
    return self._sslobj.read(len, buffer)
urllib3.exceptions.ProtocolError: ('Connection aborted.', ConnectionResetError(10054, '既存の接続はリモート ホストに強制的に切断されました。', None, 10054, None))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "main.py", line 57, in <module>
    job()
  File "main.py", line 51, in job
    UploadCSVtoBigquery(profile_label+table_date)
  File "C:\Users\masato\Documents\GitHub\yahoo_scraping\uploadCSVtoBigquery.py", line 24, in __init__
    job = client.load_table_from_file(source_file, table_ref, job_config=job_config)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\bigquery\client.py", line 1461, in load_table_from_file
    file_obj, job_resource, num_retries
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\bigquery\client.py", line 1756, in _do_resumable_upload
    stream, metadata, num_retries
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\bigquery\client.py", line 1799, in _initiate_resumable_upload
    transport, stream, metadata, _GENERIC_CONTENT_TYPE, stream_final=False
  File "C:\Users\masato\Documents\python\lib\site-packages\google\resumable_media\requests\upload.py", line 351, in initiate
    retry_strategy=self._retry_strategy,
  File "C:\Users\masato\Documents\python\lib\site-packages\google\resumable_media\requests\_helpers.py", line 116, in http_request
    return _helpers.wait_and_retry(func, RequestsMixin._get_status_code, retry_strategy)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\resumable_media\_helpers.py", line 150, in wait_and_retry
    response = func()
  File "C:\Users\masato\Documents\python\lib\site-packages\google\auth\transport\requests.py", line 216, in request
    method, url, data=data, headers=request_headers, **kwargs
  File "C:\Users\masato\Documents\python\lib\site-packages\requests\sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\masato\Documents\python\lib\site-packages\requests\sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\masato\Documents\python\lib\site-packages\requests\adapters.py", line 498, in send
    raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', ConnectionResetError(10054, '既存の接続はリモート ホストに強制的に切断されました。', None, 10054, None))
```
▽以下は15分以上の長いループで実行したときに発生

```main.py
def job():
  for i in (スクレイピングする種類の一覧):
　  for x in range(スクロール&データ取得のループ回数):
  UploadCSVtoBigquery(...)


________________________________________________________________
ログ

quit driver
upload to BigQuery
Traceback (most recent call last):
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 672, in urlopen
    chunked=chunked,
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 421, in _make_request
    six.raise_from(e, None)
  File "<string>", line 3, in raise_from
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 416, in _make_request
    httplib_response = conn.getresponse()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 1336, in getresponse
    response.begin()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 306, in begin
    version, status, reason = self._read_status()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 267, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "C:\Users\masato\Documents\python\lib\socket.py", line 589, in readinto
    return self._sock.recv_into(b)
  File "C:\Users\masato\Documents\python\lib\ssl.py", line 1071, in recv_into
    return self.read(nbytes, buffer)
  File "C:\Users\masato\Documents\python\lib\ssl.py", line 929, in read
    return self._sslobj.read(len, buffer)
ConnectionResetError: [WinError 10054] 既存の接続はリモート ホストに強制的に切断されました。

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\masato\Documents\python\lib\site-packages\requests\adapters.py", line 449, in send
    timeout=timeout
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 720, in urlopen
    method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\util\retry.py", line 400, in increment
    raise six.reraise(type(error), error, _stacktrace)
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\packages\six.py", line 734, in reraise
    raise value.with_traceback(tb)
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 672, in urlopen
    chunked=chunked,
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 421, in _make_request
    six.raise_from(e, None)
  File "<string>", line 3, in raise_from
  File "C:\Users\masato\Documents\python\lib\site-packages\urllib3\connectionpool.py", line 416, in _make_request
    httplib_response = conn.getresponse()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 1336, in getresponse
    response.begin()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 306, in begin
    version, status, reason = self._read_status()
  File "C:\Users\masato\Documents\python\lib\http\client.py", line 267, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "C:\Users\masato\Documents\python\lib\socket.py", line 589, in readinto
    return self._sock.recv_into(b)
  File "C:\Users\masato\Documents\python\lib\ssl.py", line 1071, in recv_into
    return self.read(nbytes, buffer)
  File "C:\Users\masato\Documents\python\lib\ssl.py", line 929, in read
    return self._sslobj.read(len, buffer)
urllib3.exceptions.ProtocolError: ('Connection aborted.', ConnectionResetError(10054, '既存の接続はリモート ホストに強制的に切断されました。', None, 10054, None))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\masato\Documents\python\lib\site-packages\google\auth\transport\requests.py", line 123, in __call__
    method, url, data=body, headers=headers, timeout=timeout, **kwargs
  File "C:\Users\masato\Documents\python\lib\site-packages\requests\sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Users\masato\Documents\python\lib\site-packages\requests\sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "C:\Users\masato\Documents\python\lib\site-packages\requests\adapters.py", line 498, in send
    raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', ConnectionResetError(10054, '既存の接続はリモート ホストに強制的に切断されました。', None, 10054, None))

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "main_avoidSecurityError.py", line 72, in <module>
    schedule.run_pending()
  File "C:\Users\masato\Documents\python\lib\site-packages\schedule\__init__.py", line 563, in run_pending
    default_scheduler.run_pending()
  File "C:\Users\masato\Documents\python\lib\site-packages\schedule\__init__.py", line 94, in run_pending
    self._run_job(job)
  File "C:\Users\masato\Documents\python\lib\site-packages\schedule\__init__.py", line 147, in _run_job
    ret = job.run()
  File "C:\Users\masato\Documents\python\lib\site-packages\schedule\__init__.py", line 466, in run
    ret = self.job_func()
  File "main_avoidSecurityError.py", line 59, in job
    UploadCSVtoBigquery(table_date)
  File "C:\Users\masato\Documents\GitHub\yahoo_scraping\uploadCSVtoBigquery.py", line 34, in __init__
    job = client.load_table_from_file(source_file, table_ref, job_config=job_config)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\bigquery\client.py", line 1461, in load_table_from_file
    file_obj, job_resource, num_retries
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\bigquery\client.py", line 1756, in _do_resumable_upload
    stream, metadata, num_retries
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\bigquery\client.py", line 1799, in _initiate_resumable_upload
    transport, stream, metadata, _GENERIC_CONTENT_TYPE, stream_final=False
  File "C:\Users\masato\Documents\python\lib\site-packages\google\resumable_media\requests\upload.py", line 351, in initiate
    retry_strategy=self._retry_strategy,
  File "C:\Users\masato\Documents\python\lib\site-packages\google\resumable_media\requests\_helpers.py", line 116, in http_request
    return _helpers.wait_and_retry(func, RequestsMixin._get_status_code, retry_strategy)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\resumable_media\_helpers.py", line 150, in wait_and_retry
    response = func()
  File "C:\Users\masato\Documents\python\lib\site-packages\google\auth\transport\requests.py", line 212, in request
    self._auth_request, method, url, request_headers
  File "C:\Users\masato\Documents\python\lib\site-packages\google\auth\credentials.py", line 124, in before_request
    self.refresh(request)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\oauth2\service_account.py", line 334, in refresh
    access_token, expiry, _ = _client.jwt_grant(request, self._token_uri, assertion)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\oauth2\_client.py", line 146, in jwt_grant
    response_data = _token_endpoint_request(request, token_uri, body)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\oauth2\_client.py", line 105, in _token_endpoint_request
    response = request(method="POST", url=token_uri, headers=headers, body=body)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\auth\transport\requests.py", line 128, in __call__
    six.raise_from(new_exc, caught_exc)
  File "<string>", line 3, in raise_from
google.auth.exceptions.TransportError: ('Connection aborted.', ConnectionResetError(10054, '既存の接続はリモート ホストに強制的に切断されました。', None, 10054, None))
```
##対処した内容
これにはかなり詰まりました。

ジョブのループ実行をpythonの「shcedule」ライブラリで行っていたが、 **WindowsOSの「タスクスケジューラー」** を使うことで再現しなくなりました。

##課題
もっとちゃんとした方法があると思いたいので、これは暫定対処という扱いです。勉強兼ねて根本原
因・解決方法を今後調べます。

（また、タスクスケジューラーでの実行でエラーになっても、少なくともライブラリ「pysnooper」ではログが吐き出されなかった。このままだとエラーを検知できないので暇なときにでも確認したい）

```import pysnooper
@pysnooper.snoop(r"C:\Users\...\log.txt")```

# 環境変数がリセットされる
##前提
BigQuery（またはCloudStorage）にデータを送るためにはそれらのAPIクライアントを使うための認証を通す必要がある。そのために認証キーのパスを設定することが前提。
>環境変数 GOOGLE_APPLICATION_CREDENTIALS を設定して、アプリケーション コードに認証情報を指定します。
[BigQuery クライアント ライブラリ>認証の設定](https://cloud.google.com/bigquery/docs/reference/libraries?hl=ja#setting_up_authentication
)

##問題
しかし、上記公式ドキュメントに記載の通り、
>この変数は現在のシェル セッションにのみ適用されるため、新しいセッションを開く場合は、変数を再度設定してください。

なのでセッションが切れた場合は、以下のように「GOOGLE![undefined]()
_APPLICATION_CREDENTIALS」を環境変数としてセットしていないときのエラーが出ます。

```
C:\Users\masato\Documents\GitHub\yahoo_scraping>py uploadCSVtoBigquery.py
Traceback (most recent call last):
  File "uploadCSVtoBigquery.py", line 2, in <module>
    client = bigquery.Client()
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\bigquery\client.py", line 176, in __init__
    project=project, credentials=credentials, _http=_http
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\client.py", line 226, in __init__
    _ClientProjectMixin.__init__(self, project=project)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\client.py", line 178, in __init__
    project = self._determine_default(project)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\client.py", line 193, in _determine_default
    return _determine_default_project(project)
  File "C:\Users\masato\Documents\python\lib\site-packages\google\cloud\_helpers.py", line 186, in _determine_default_project
    _, project = google.auth.default()
  File "C:\Users\masato\Documents\python\lib\site-packages\google\auth\_default.py", line 321, in default
    raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)
google.auth.exceptions.DefaultCredentialsError: Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started
```
今回はループ実行をpythonの「shcedule」ライブラリからWindowsOSの「タスクスケジューラー」を使うことにより、ジョブの定期実行ごとにコマンドプロンプトのウィンドウが立ち上がるようになり、つまりはセッションがリセットされるようになりました。

また副産物として、Windowsで搭載されているツールなのでログオフ時の挙動やジョブ実行時にスリープを解除したりといったOSの制御も可能になりました。こういったものはシステム的に役割分離したほうが良いのかな、というのが学びです。



##対処した内容
実行ファイルで認証キーのパスを設定する方針を立てました。
クライアントを立てる前に **osライブラリ** で認証キーのパスを指定することで認証が通りました。

```
import os
from google.cloud import bigquery
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = r'C:\Users\masato\Documents\GitHub\credentials\bananapj-adc1fd3f90dd.json'
client = bigquery.Client()
```

##課題
特になし


---
何かあればお手数ですが、本記事か以下アカウントでお知らせください！

**\ Follow Me! /**
[**Qiitaアカウント**](https://qiita.com/MasatoEmata)
[**Twitterアカウント**](https://twitter.com/masatoemata)