PyODPS を使用して Shence イベント データを収集する


I.はじめに

最近、あるWebサイトへのユーザーのアクセス状況を確認したいという依頼を受けましたが、Webサイトのアクセスデータは、ユーザー識別(ユーザーID)の一部制限のためにShenceに埋め込まれており、データは隔離されており、Shence経由で直接アクセスすることはできませんこの要件を解決するには、まず Shence データをデータベースに保存し、それをデータベース内の他のユーザー データと接続して、より完全なユーザー ポートレート情報を取得する必要があります。
この記事では主に、Shence データをデータベースに保存する関連操作を紹介します。
目標: Shence データを MaxCompute データ ウェアハウスに保存します。

2. データの収集、処理、保管

基本ロジックは、Shence データをリクエストする -> データを処理する -> MaxCompute 配列に変換してデータベースに保存するというものです。
Shence データのリクエストには、トークンの取得、Shence API (公式ドキュメント) の取得、API のデバッグなどが含まれます。データの処理には、配列指向の考え方、タイムゾーンの問題への対処、Key なしの辞書値メソッドなどの処理が含まれます。MaxCompute に変換されます。配列 データベースへのマージには、MaxCompute テーブルの作成、データの書き込み、スケジュール パラメーターの使用、スケジュール構成などの問題が含まれます。以下で一つずつ解決していきましょう。

2.1 Shenceトークンの取得

ここでのトークンは API シークレットのトークンであり、管理者が取得する必要があります。

2.2 Shenceデータのリクエスト

ここではデバッグ作業に Postman を使用します。

ドキュメントによると、イベント データをエクスポートするためのインターフェイス情報は次のとおりです。

関連センサー データ エクスポート API ドキュメント リファレンス: https://manual.sensorsdata.cn/sa/latest/zh_cn/tech_export_transfer-150668708.html

curl 'https://saasdemo.cloud.sensorsdata.cn/api/sql/query?token=******&project=default' \
-X POST \
--data-urlencode "q=SELECT * FROM events where date = '2017-01-01' /*MAX_QUERY_EXECUTION_TIME=1800*/" \
--data-urlencode "format=event_json" \
>> event.json

変更する必要があるもの:

ドメイン名: https://saasdemo.cloud.sensorsdata.cn 独自のドメイン名に変更する必要がありますが、Shence 公式プロジェクトにログイン後、アドレスバーの前の文字列 (以下のように)
トークン: を参照してください。 [2.1 Shence トークンの取得]
プロジェクトへ: リンク パラメーターが表示されます (以下を参照)

画像.png

Postman 経由でリクエストが成功したら、右側の [</>] ロゴをクリックしてソース コードを見つけます。これはデータ リクエストのデモです。多くの言語をサポートしており、必要に応じて取得できます。
画像.png
今回はPythonを使って実装するのでPythonのデモをとってみます。

import requests

url = "https://【你的域名】/api/sql/query?token=【你的API Secret】&project=【你的项目】"

payload = {
    
    'q': 'SELECT * FROM events  where date = \'2023-11-20\' limit 20 /*MAX_QUERY_EXECUTION_TIME=1800*/',
           'format': 'event_json'}
files=[

]
headers = {
    
    
    'Content-Type': 'application/x-www-form-urlencoded',
}

response = requests.request("POST", url, headers=headers, data=payload, files=files)

print(response.text)

リクエストを開始した後、Shence によって返されるデータ構造は大まかに次のとおりです。次のデータは感度が解除され、のみ保持されています: typeeventtime_freetimedistinct_idproperties合計 6 つのキー、およびフィールドには合計 、propertiesも含まれます。サブキーは5つ。$os$os_version$province$city$browser$is_first_time

{"type":"track","event":"$pageview","time_free":true,"time":1700448379460,"distinct_id":"483855yhbjafigngduef1","properties":{"$os_version":"16.6.1","$city":"成都","$os":"iOS","$is_first_time":false,"$browser":"Mobile Safari","$country":"中国","$province":"四川"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700448381061,"distinct_id":"483855yhbjafigngduef1","properties":{"$os_version":"16.6.1","$city":"株洲","$os":"iOS","$is_first_time":false,"$browser":"Mobile Safari","$country":"中国,"$province":"湖南"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700466642669,"distinct_id":"483855yhbjafigngduef1","properties":{"$os_version":"16.6.1","$city":"长沙","$os":"iOS","$is_first_time":false,"$browser":"Mobile Safari","$country":"中国","$province":"湖南"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700466642906,"distinct_id":"483855yhbjafigngduef1","properties":{"$os_version":"16.6.1","$city":"温州","$os":"iOS","$is_first_time":false,"$browser":"Mobile Safari","$country":"中国","$province":"浙江"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700466644391,"distinct_id":"483855yhbjafigngduef1","properties":{"$os_version":"16.6.1","$city":"济南","$os":"iOS","$is_first_time":false, "$browser":"Mobile Safari","$country":"中国","$province":"山东"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700485319088,"distinct_id":"483855yh1jafigngdue3d","properties":{"$browser_version":"7.0.20.1781","$os_version":"10","$city":"北京","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$country":"中国","$province":"北京"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700493761576,"distinct_id":"483855yh1jafigngdue3d","properties":{"$browser_version":"7.0.20.1781","$os_version":"10","$city":"上海","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$country":"中国","$province":"上海"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700493779926,"distinct_id":"483855yh1jafigngdue3d","properties":{"$browser_version":"7.0.20.1781","$os_version":"10","$city":"深圳","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$country":"中国","$province":"广东"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700493823995,"distinct_id":"483855yh1jafigngdue3d","properties":{"$browser_version":"7.0.20.1781","$os_version":"10","$city":"北京","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$country":"中国","$province":"北京"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700493863222,"distinct_id":"483855yh1jafigngdue3d","properties":{"$browser_version":"7.0.20.1781","$os_version":"10","$city":"广州","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$country":"中国","$province":"广东"}}

2.3 データ処理配列指向

データ処理により、データは最終的に次の形式に処理されます。画像.png
データ間の関係は次のようにマッピングされます。

パンダコラム 応答データキー
イベント イベント
時間 時間
アクション時間 時間までに
ポイント 時間でフォーマットされた
個別の ID 個別の ID
OS $os
OS_バージョン $os_version
$province
$city
ブラウザ $ブラウザ
is_first_time $is_first_time

typeここでは、 、の 2 つのキーが破棄されtime_free、その後、他の 8 つのキーが独立した列に処理され、フィールド時刻が人間が判読できる時刻形式 (「年-月-日 時: 分: 秒」) に処理されてフォーマットされます。日付形式 (「年、月、日」) に変換して、その後の保存とテーブル パーティションとしての使用を容易にします。特記事項: Pandas のデフォルトの期間はタイムゾーン 0 であるため、**time**タイムスタンプを北京時間に変換する場合は、必ず 8 時間を追加してください。

処理ロジックを明確にした後、データ処理を開始します。データを処理するにはさまざまな方法があります。ここでは配列指向のアプローチを使用します。一般的なロジックは、要求されたデータを配列に直接変換し、その配列を処理に使用することです。今すぐ:

# 按行切割响应数据
res_datas = text.split('\n')
# 转为 json 数据(字符串),并转换为 DataFrame
datas_df = pd.read_json(json.dumps(res_datas))
# 将列数据转为字典,并修改列名为 datas
datas_df = pd.DataFrame(datas_df[0][datas_df[0] !=''].apply(lambda x:json.loads(x)))
datas_df.columns = ['datas']

Shence から返されたデータを処理する際の困難は、タイム ゾーンの問題とサブフィールドの逆アセンブリ (重要な点は、一部のレコード キーが不完全であることです) です。
立ち止まって考えてみましょう。もしそれがあなただったら、どう対処しますか?

[タイム ゾーンの問題] Pandas は、タイムスタンプと時刻形式の間で変換するデータを処理するときに、デフォルトで 0 タイム ゾーンを使用します。タイムスタンプや時刻を直接加算、減算、乗算、除算するなど、変換の問題が関係しない場合、 2 つの間の変換が関与すると、タイム ゾーンの処理が必要になります。
ここで提供される変換ソリューションは、8 時間を直接追加すること、つまりdatetime64[ns]type 列に直接追加することですpd.Timedelta(hours=8))参考文献は以下の通りです。

#注意时区问题!!!
datas_df['action_time'] = pd.to_datetime((pd.to_datetime(datas_df.time,unit='ms')+ pd.Timedelta(hours=8)).dt.strftime('%Y-%m-%d %H:%M:%S')) #用于记录时间
datas_df['pt'] = (pd.to_datetime(datas_df.time,unit='ms')+ pd.Timedelta(hours=8)).dt.strftime('%Y%m%d')  #用于分区

[サブフィールドの問題] サブフィールドの分解自体は難しくありませんが、一部のレコードのキーが欠落していることが難しいのですが、辞書オブジェクトに適切な解決策があり、それを使用してget('[字段名]', '')対処します。

#有的key没有,使用:dict.get('[字段名]', '')
datas_df['os'] = datas_df.datas.apply(lambda x:x['properties'].get('$os', ''))
datas_df['os_version'] = datas_df.datas.apply(lambda x:x['properties'].get('$os_version', ''))
datas_df['province'] = datas_df.datas.apply(lambda x:x['properties'].get('$province', ''))
datas_df['city'] = datas_df.datas.apply(lambda x:x['properties'].get('$city', ''))
datas_df['browser'] = datas_df.datas.apply(lambda x:x['properties'].get('$browser', ''))
datas_df['is_first_time'] = datas_df.datas.apply(lambda x:x['properties'].get('$is_first_time', ''))

最終的な配列指向処理メソッドの完全なコードは次のとおりです (直接実行できます)。


def dates_processing(text):
    """处理响应的数据"""
    import pandas as pd
    import json

    # 按行切割响应数据
    res_datas = text.split('\n')
    # 转为 json 数据(字符串),并转换为 DataFrame
    datas_df = pd.read_json(json.dumps(res_datas))
    # 将列数据转为字典,并修改列名为 datas
    datas_df = pd.DataFrame(datas_df[0][datas_df[0] !=''].apply(lambda x:json.loads(x)))
    datas_df.columns = ['datas']
    # 展开 datas,将数据取出,作为新列
    datas_df['event'] = datas_df.datas.apply(lambda x:x['event'])
    datas_df['time'] = datas_df.datas.apply(lambda x:x['time'])
    #注意时区问题!!!
    datas_df['action_time'] = pd.to_datetime((pd.to_datetime(datas_df.time,unit='ms')+ pd.Timedelta(hours=8)).dt.strftime('%Y-%m-%d %H:%M:%S')) #用于记录时间
    datas_df['pt'] = (pd.to_datetime(datas_df.time,unit='ms')+ pd.Timedelta(hours=8)).dt.strftime('%Y%m%d')  #用于分区
    datas_df['distinct_id'] = datas_df.datas.apply(lambda x:x['distinct_id'])

    #有的key没有,使用:dict.get('[字段名]', '')
    datas_df['os'] = datas_df.datas.apply(lambda x:x['properties'].get('$os', ''))
    datas_df['os_version'] = datas_df.datas.apply(lambda x:x['properties'].get('$os_version', ''))
    datas_df['province'] = datas_df.datas.apply(lambda x:x['properties'].get('$province', ''))
    datas_df['city'] = datas_df.datas.apply(lambda x:x['properties'].get('$city', ''))
    datas_df['browser'] = datas_df.datas.apply(lambda x:x['properties'].get('$browser', ''))
    datas_df['is_first_time'] = datas_df.datas.apply(lambda x:x['properties'].get('$is_first_time', ''))
    return datas_df

response_text = '''
{"type":"track","event":"$pageview","time_free":true,"time":1700448379460,"distinct_id":"100101","properties":{"$os_version":"16.6.1","$city":"成都","$os":"iOS","$is_first_time":false,"$province":"四川"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700448381061,"distinct_id":"100102","properties":{"$os_version":"16.6.1","$city":"株洲","$os":"iOS","$is_first_time":false,"$province":"湖南"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700466642669,"distinct_id":"100103","properties":{"$os_version":"16.6.1","$city":"长沙","$os":"iOS","$is_first_time":false,"$province":"湖南"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700466642906,"distinct_id":"100104","properties":{"$os_version":"16.6.1","$city":"温州","$os":"iOS","$is_first_time":false,"$province":"浙江"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700466644391,"distinct_id":"100105","properties":{"$os_version":"16.6.1","$city":"济南","$os":"iOS","$is_first_time":false,"$province":"山东"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700485319088,"distinct_id":"100106","properties":{"$os_version":"10","$city":"北京","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$province":"北京"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700493761576,"distinct_id":"100107","properties":{"$os_version":"10","$city":"上海","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$province":"上海"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700493779926,"distinct_id":"100108","properties":{"$os_version":"10","$city":"深圳","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$province":"广东"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700493823995,"distinct_id":"100109","properties":{"$os_version":"10","$city":"北京","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$province":"北京"}}
{"type":"track","event":"$pageview","time_free":true,"time":1700493863222,"distinct_id":"100110","properties":{"$os_version":"10","$city":"广州","$os":"Windows","$is_first_time":false,"$browser":"WeChat","$province":"广东"}}
'''
dates_processing(response_text)

2.4 Alibaba Cloud DataFrame ストレージのテスト

まず、PyODPS に付属する DataFrame と Pandas の DataFrame という 2 つの概念を理解します。この 2 つは異なります。
PyODPS が提供する DataFrame API は Pandas に似たインターフェイスを備えていますが、データ処理能力は Pandas が提供する DataFrame ほど強力ではありません。PyODPS に付属する DataFrame は、MaxCompute の計算能力を最大限に活用できます。

このテストのデータ フローは、辞書dict_test->Pandas データフレーム - datas_df>PyODPS データフレーム - pyodps_df>MaxCompute フォームですproject.my_new_table

まず、上記で処理したデータを使用して 3 つの項目を辞書形式で返し、結果をコピーして、dict_testテスト用に個別に使用するように割り当てます。

datas_df[:3].to_dict()

dict_test構築に使用される変数は、Pandas DataFrame タイプの form ですdatas_dfdatas_dfこれは、Shence から要求されたデータを処理した後の実際の結果と一致しており、同じ変数名を使用しているため、このテストに合格した後、コードの小さな部分を直接追加できます。外。datas_dfPyODPS の DataFrame 配列を作成するために使用されますpyodps_df

dic = {
    
    'datas': {
    
    1: {
    
    'type': 'track','event': '$pageview','time_free': True,'time': 1700448379460,'distinct_id': '100101','properties': {
    
    '$os_version': '16.6.1','$city': '成都','$os': 'iOS','$is_first_time': False,'$province': '四川'}},
                 2: {
    
    'type': 'track','event': '$pageview','time_free': True,'time': 1700448381061,'distinct_id': '100102','properties': {
    
    '$os_version': '16.6.1','$city': '株洲','$os': 'iOS','$is_first_time': False,'$province': '湖南'}},
                 3: {
    
    'type': 'track','event': '$pageview','time_free': True,'time': 1700466642669,'distinct_id': '100103','properties': {
    
    '$os_version': '16.6.1','$city': '长沙','$os': 'iOS','$is_first_time': False,'$province': '湖南'}}},
       'event': {
    
    1: '$pageview', 2: '$pageview', 3: '$pageview'},
       'time': {
    
    1: 1700448379460, 2: 1700448381061, 3: 1700466642669},
       'action_time': {
    
    1: '2023-11-20 10:46:19',2: '2023-11-20 10:46:21',3: '2023-11-20 15:50:42'},
       'pt': {
    
    1: '20231120', 2: '20231120', 3: '20231120'},
       'distinct_id': {
    
    1: '100101', 2: '100102', 3: '100103'},
       'os': {
    
    1: 'iOS', 2: 'iOS', 3: 'iOS'},
       'os_version': {
    
    1: '16.6.1', 2: '16.6.1', 3: '16.6.1'},
       'province': {
    
    1: '四川', 2: '湖南', 3: '湖南'},
       'city': {
    
    1: '成都', 2: '株洲', 3: '长沙'},
       'browser': {
    
    1: '', 2: '', 3: ''},
       'is_first_time': {
    
    1: False, 2: False, 3: False}
      }
datas_df = pd.DataFrame(dic)

変数はpyodps_dfPyODPS に付属の DataFrame 型です。エラーを避けるために、データ型を指定し、列名と対応するデータ型をas_typeパラメーターに渡す必要があります。

pyodps_df = DataFrame(datas_df,as_type={
    
    
    "event"			    : "string"
    ,"time"				: "int64"
    ,"time_free"		: "boolean"
    ,"distinct_id"	    : "string"
    ,"os"				: "string"
    ,"os_version"		: "string"
    ,"province"			: "string"
    ,"city"				: "string"
    ,"browser"			: "string"
    ,"is_first_time"    : "boolean"
    ,'pt'               : "string"
})

変数はargs、スケジュール構成のパラメータです。テスト中に、状況に基づいて選択します。データワークスでデバッグしている場合は、パラメータ セクションで直接構成し、変数をコメント アウトできます。変数をリリースした後にコメント アウトする必要があります。バグを避けるためにスケジュールを立てます。

DataWorks の PyODPS ノードには、ODPS エントリであるグローバル変数 odps または o が含まれます。o.create_table()したがって、フォームを作成するために直接使用できます。([表名],('字段1 数据类型,字段2 数据类型','分区字段 数据类型'),if_not_exists=True)パラメーターのうち、2 番目のパラメーターはタプルであることに注意してください。通常のフィールドはタプルの最初の値としてスペースで結合され、パーティション フィールドはタプルの最初の値として使用されます。 2 番目の値。

#创建分区表my_new_table,(表字段列表,分区字段列表)。
table = o.create_table(out_table, ('event string,time bigint,time_free boolean,distinct_id string,os string,os_version string,province string,city string,browser string,is_first_time boolean', 'pt string'), if_not_exists=True)
# 注意:该写入方式要求 DataFrame 的每个字段的类型都必须相同

テーブルを構築するときは、変換可能なデータ型と DataFrame データ型にも注意する必要があります。そうでないとエラーが報告されます。DataFrame 列タイプと ODPS SQL フィールド タイプのマッピング表を参照してください: https://help.aliyun.com/zh/maxcompute/user-guide/sequence#section-avk-4s4-cfb
画像.png

テーブルが作成されたら、それを使用してo.exist_table(out_table)フォームが存在するかどうかを判断し、存在する場合は返しますTrueテーブルが存在する場合、実行結果は Maxcompute テーブルにレポートされます。
Maxcompute テーブルに保存するには 2 つの書き込み方法があります。1 つは、DataFrame 内の列をパーティション フィールドとして指定し、パラメータの名前がpartitions であることに注意する方法です。もう 1 つは、パラメータpartition を使用してパーティションを指定し、 2 つのパラメータは接尾辞 s によって異なります
2 番目の方法では、パラメーターを追加できますcreate_partition=True。つまり、パーティションが存在しない場合に新しいパーティションを作成します。

if o.exist_table(out_table):
    # 向表插入数据方式一:指定DataFrame的列为分区字段
    # pyodps_df.persist(out_table, partitions=['pt']) #指定某个字段就是分区字段
    # 向表插入数据方式二:指定分区
    pyodps_df.persist(out_table, partition="pt=%s"% args['pt'], create_partition=True) #如果不存在则创建分区
    print('完成写入!')
# 将执行结果保存为MaxCompute表文档链接:https://help.aliyun.com/zh/maxcompute/user-guide/execution#section-jwh-1y4-cfb
else:
    print(f'表单{
      
      out_table}不存在。')

これら 2 つの方法の適用について考える: 大量のデータを実行する場合は、基本的に最初の方法を使用します。以前に処理されたptフィールドを指定して、1 日あたり 1 つのパーティションの形式でデータを書き込むことができるためです。増分データを実行している場合は、両方の方法を使用できます。フルボリュームでもインクリメンタルでも、重要なポイントは 1 つあります。データをリクエストする場合は、日付の値を設定する必要があります。フルボリュームを実行する場合は、必要な期間に応じて設定します。インクリメンタルを実行する場合は、データのリクエストを検討できます。日常的に。詳細については、[2.5 スケジュール設計と構成] を参照してください。

完全なテスト ストレージのコードは次のとおりです (直接実行できます)。

from odps.df import DataFrame

dic = {
    
    'datas': {
    
    1: {
    
    'type': 'track','event': '$pageview','time_free': True,'time': 1700448379460,'distinct_id': '100101','properties': {
    
    '$os_version': '16.6.1','$city': '成都','$os': 'iOS','$is_first_time': False,'$province': '四川'}},
                 2: {
    
    'type': 'track','event': '$pageview','time_free': True,'time': 1700448381061,'distinct_id': '100102','properties': {
    
    '$os_version': '16.6.1','$city': '株洲','$os': 'iOS','$is_first_time': False,'$province': '湖南'}},
                 3: {
    
    'type': 'track','event': '$pageview','time_free': True,'time': 1700466642669,'distinct_id': '100103','properties': {
    
    '$os_version': '16.6.1','$city': '长沙','$os': 'iOS','$is_first_time': False,'$province': '湖南'}}},
       'event': {
    
    1: '$pageview', 2: '$pageview', 3: '$pageview'},
       'time': {
    
    1: 1700448379460, 2: 1700448381061, 3: 1700466642669},
       'action_time': {
    
    1: '2023-11-20 10:46:19',2: '2023-11-20 10:46:21',3: '2023-11-20 15:50:42'},
       'pt': {
    
    1: '20231120', 2: '20231120', 3: '20231120'},
       'distinct_id': {
    
    1: '100101', 2: '100102', 3: '100103'},
       'os': {
    
    1: 'iOS', 2: 'iOS', 3: 'iOS'},
       'os_version': {
    
    1: '16.6.1', 2: '16.6.1', 3: '16.6.1'},
       'province': {
    
    1: '四川', 2: '湖南', 3: '湖南'},
       'city': {
    
    1: '成都', 2: '株洲', 3: '长沙'},
       'browser': {
    
    1: '', 2: '', 3: ''},
       'is_first_time': {
    
    1: False, 2: False, 3: False}
      }
datas_df = pd.DataFrame(dic)
pyodps_df = DataFrame(datas_df,as_type={
    
    
    "event"				: "string"
    ,"time"				: "int64"
    ,"time_free"		: "boolean"
    ,"distinct_id"	    : "string"
    ,"os"				: "string"
    ,"os_version"		: "string"
    ,"province"			: "string"
    ,"city"				: "string"
    ,"browser"			: "string"
    ,"is_first_time"    : "boolean"
    ,'pt'               : "string"
})
out_table = 'project.my_new_table'
# 调度配置的参数,发布调度之后需要注释掉
args = {
    
    'pt':'20231120'}

# DataWorks的PyODPS节点中,将会包含一个全局变量odps或者o,即为ODPS入口。
# ODPS入口相关文档链接:https://help.aliyun.com/zh/maxcompute/user-guide/use-pyodps-in-dataworks

#创建分区表my_new_table,(表字段列表,分区字段列表)。
table = o.create_table(out_table, ('event string,time bigint,time_free boolean,distinct_id string,os string,os_version string,province string,city string,browser string,is_first_time boolean', 'pt string'), if_not_exists=True)
# 注意:该写入方式要求 DataFrame 的每个字段的类型都必须相同
# DataFrame 列类型和 ODPS SQL 字段类型映射表:https://help.aliyun.com/zh/maxcompute/user-guide/sequence#section-avk-4s4-cfb
if o.exist_table(out_table):
    # 向表插入数据方式一:指定DataFrame的列为分区字段
    # pyodps_df.persist(out_table, partitions=['pt']) #指定某个字段就是分区字段
    # 向表插入数据方式二:指定分区
    pyodps_df.persist(out_table, partition="pt=%s"% args['pt'], create_partition=True) #如果不存在则创建分区
    print('完成写入!')
# 将执行结果保存为MaxCompute表文档链接:https://help.aliyun.com/zh/maxcompute/user-guide/execution#section-jwh-1y4-cfb
else:
    print(f'表单{
      
      out_table}不存在。')

2.5 スケジュールの設計と構成

これについては最後に説明しますが、関連フィールドの処理と完全なテストへの参加が必要となるため、実際にはプロセス全体にわたって行われます。
「データを加工してデータベースに入れる」という状況に陥るよりも、「データをどのようにデータベースに入れるか」を事前にしっかりと考えておくことができれば、処理中のテストを減らして一発でテストをパスすることができます。すべてのテストが完了し、最終的に本番環境の実行がスケジュールされました。フィールドを追加し、すべてを再度テストして検証する必要があることがわかりました。

スケジューリングの設計は一般に需要に関連しており、スケジューリング頻度は、T+1 更新、1 時間に 1 回の更新、5 分に 1 回の更新など、需要に応じて構成されます。
この実用的なプロジェクトの需要は高くないため、更新のスケジュール頻度は T+1 に設定されています。
したがって、構成パラメータは次のようになります。pt=$[yyyymmdd-1]、データを 1 日 1 回実行し、指定されたパーティションに書き込むだけです。
画像.png

ここにはもう 1 つの詳細な点があります: Shence データをリクエストするとき、whereリクエストされた日付の範囲を制限するための入力 SQL の条件があります。現在、固定パラメーターが構成されています2023-11-20
画像.png
このパラメータは調整して動的に変更できるパラメータに変更する必要があります。変更方法は 2 つあります。1
つ目は、SQL を直接使用して T-1 の日付を取得する方法です。CURRENT_DATE() - INTERVAL '1' day

センサー機能リファレンスドキュメントリンク: https://manual.sensorsdata.cn/sa/latest/zh_cn/page-137920660.html

もう 1 つは、新しいスケジュール パラメータを追加し、y_m_d=$[yyyy-mm-dd-1]置き換えることです。
どの戦略を採用すべきでしょうか? 大丈夫ですよ。しかし、私は 2 番目の方法を選択し、2 番目の方法もお勧めします。理由は非常に単純です。ある日何か問題が発生してデータを補充する必要がある場合、操作が簡単です。最初にコードを変更せずにパラメータの日付を変更します。カウントを実行し、実行後に値を元に戻し、送信して公開します。
新しいスケジュール パラメータは次のとおりです:
注: パーティション フィールドの形式は日付値に関連付けられて年月日おらず-、コード内の日付形式にはそれが必要なので、別のパラメータを作成する必要があります。もちろん、形式を次のように年-月-日使用することもできます。年-月-日パーティション フィールドなので、2 つのパラメータを設定する必要はありませんが、特別な注意を払う必要があります。以前のパーティション フィールドはフォーマットptに処理されます年月日。それらを変更したい場合は、全体を変更して確実にフォーマットする必要があります。統一フォーマット。
画像.png
スケジュール パラメーターの書式設定のリファレンス ドキュメント: https://help.aliyun.com/zh/dataworks/user-guide/supported-formats-of-scheduling-parameters

2.6 プロジェクトコードの統合

スケジュールパラメータを設定します。
画像.png

完全なコード:

import requests
import pandas as pd
import json
from odps.df import DataFrame

def request_datas():
    """请求数据"""
    url = "https://【你的域名】/api/sql/query?token=【你的API Secret】&project=【你的项目】"
    sql = """select * from events where date = '%s' """
    payload = {
    
    'q': f'{
      
      sql} /*MAX_QUERY_EXECUTION_TIME=1800*/' % args['y_m_d'],
            'format': 'event_json'}
    headers = {
    
    'Content-Type': 'application/x-www-form-urlencoded',}

    response = requests.request("POST", url, headers=headers, data=payload)
    print('完成读取!')
    if response.status_code == 200:
        print('数据正常获取!')
        return response.text
    else:
        print('数据获取异常!')
        return None


def dates_processing(text):
    """处理响应的数据"""
    # 按行切割响应数据
    res_datas = text.split('\n')
    # 转为 json 数据(字符串),并转换为 DataFrame
    datas_df = pd.read_json(json.dumps(res_datas))
    # 将列数据转为字典,并修改列名为 datas
    datas_df = pd.DataFrame(datas_df[0][datas_df[0] !=''].apply(lambda x:json.loads(x)))
    datas_df.columns = ['datas']
    # 展开 datas,将数据取出,作为新列
    datas_df['event'] = datas_df.datas.apply(lambda x:x['event'])
    datas_df['time'] = datas_df.datas.apply(lambda x:x['time'])
    #注意时区问题!!!
    datas_df['action_time'] = pd.to_datetime((pd.to_datetime(datas_df.time,unit='ms')+ pd.Timedelta(hours=8)).dt.strftime('%Y-%m-%d %H:%M:%S')) #用于记录时间
    datas_df['pt'] = (pd.to_datetime(datas_df.time,unit='ms')+ pd.Timedelta(hours=8)).dt.strftime('%Y%m%d')  #用于分区
    datas_df['distinct_id'] = datas_df.datas.apply(lambda x:x['distinct_id'])

    #有的key没有,使用:dict.get('[字段名]', '')
    datas_df['os'] = datas_df.datas.apply(lambda x:x['properties'].get('$os', ''))
    datas_df['os_version'] = datas_df.datas.apply(lambda x:x['properties'].get('$os_version', ''))
    datas_df['province'] = datas_df.datas.apply(lambda x:x['properties'].get('$province', ''))
    datas_df['city'] = datas_df.datas.apply(lambda x:x['properties'].get('$city', ''))
    datas_df['browser'] = datas_df.datas.apply(lambda x:x['properties'].get('$browser', ''))
    datas_df['is_first_time'] = datas_df.datas.apply(lambda x:x['properties'].get('$is_first_time', ''))
    return datas_df


if __name__ == '__main__':
    response_datas = request_datas()
    if response_datas is not None:
        datas_df = data_preprocessing(response_datas)
        size = datas_df.shape[0]
        pyodps_df = DataFrame(datas_df,as_type={
    
    
            "event"			: "string"
            ,"time"				: "int64"
            ,"time_free"		: "boolean"
            ,"distinct_id"	    : "string"
            ,"os"				: "string"
            ,"os_version"		: "string"
            ,"province"			: "string"
            ,"city"				: "string"
            ,"browser"			: "string"
            ,"is_first_time"    : "boolean"
            ,'pt'               : "string"
        })
        out_table = '【修改为你的项目和表单,示例:project.my_new_table】'
        # 调度配置的参数,发布调度之后需要注释掉
		# args = {'pt':'20231120', 'y_m_d':'2023-11-20'}
        # DataWorks的PyODPS节点中,将会包含一个全局变量odps或者o,即为ODPS入口。
        # ODPS入口相关文档链接:https://help.aliyun.com/zh/maxcompute/user-guide/use-pyodps-in-dataworks
        # 创建分区表my_new_table,(表字段列表,分区字段列表)。
        table = o.create_table(out_table, ('event string,time bigint,time_free boolean,distinct_id string,os string,os_version string,province string,city string,browser string,is_first_time boolean', 'pt string'), if_not_exists=True)
        # 注意:该写入方式要求 DataFrame 的每个字段的类型都必须相同
        # DataFrame 列类型和 ODPS SQL 字段类型映射表:https://help.aliyun.com/zh/maxcompute/user-guide/sequence#section-avk-4s4-cfb

        if o.exist_table(out_table):
            # 向表插入数据方式一:指定DataFrame的列为分区字段
            # pyodps_df.persist(out_table, partitions=['pt']) #指定某个字段就是分区字段
            # 向表插入数据方式二:指定分区
            pyodps_df.persist(out_table, partition="pt=%s"% args['pt'], create_partition=True) #如果不存在则创建分区
            print(f'完成写入!共 {
      
      size} 条数据。')
        # 将执行结果保存为MaxCompute表文档链接:https://help.aliyun.com/zh/maxcompute/user-guide/execution#section-jwh-1y4-cfb
        else:
            print(f'表单{
      
      out_table}不存在。')
    else:
        print('退出程序!')

注:
1.【】ファイル内の内容を変更して独自の内容に変更する必要があります。そうしないと、エラーが報告されます。
2. 現在は比較的基本的なフィールドを処理していますが、他のフィールドを追加する必要がある場合は、datas_df['os'] = datas_df.datas.apply(lambda x:x['properties'].get('$os', ''))次のような形式に従って追加できますdatas_df['browser_version'] = datas_df.datas.apply(lambda x:x['properties'].get('$browser_version', ''))
3. MaxCompute プロジェクトはデータ保護ポリシーをオフにする必要があります。オフにしないと外部データを導入できません。「プロジェクト 'xxx' は保護されています」というエラー メッセージが表示されます。参考ソリューション: https://help.aliyun.com/zh/maxcompute/user-guide/odps-0130013

3. まとめ

この記事を数日間書き続けていますが、まだうまく理解できません。3日か4日かかったのですが、途中で落とし穴をたくさん感じて、さらに他の作業もあったので、落とし穴を踏んだり抜け出したりしながら、断続的に作業を続けました。幸いなことに、私はついに罠から抜け出し、書き終えることができました。

最終的なソリューションのプロセス全体は、実際には、Shence データをリクエストする -> Pandas がデータを処理する -> MaxCompute 配列に変換し、データベースにマージするというものです。

  • Shence データノードのコンテンツのリクエストには以下が含まれます
    • トークンの取得;
    • Shence APIを取得します。
    • Postman を使用して API をデバッグし、コード デモを取得します。
  • Panda の処理データ ノードのコンテンツには次のものがあります。
    • 配列指向の思考を使用してデータを処理します。
    • タイムゾーンの問題に対処する。
    • ディクショナリにはキー値メソッドがありません。
    • データストレージのパーティションパラメータを構成します。
  • MaxCompute 配列に変換し、ライブラリ ノードにマージします。内容は次のとおりです。
    • さまざまなデータフレームの類似点と相違点を理解します。
    • MaxCompute はテーブルを作成します。
    • PyODPS Dataframeデータ書き込み(persist())
    • パラメータの設定と使用方法をスケジュールします。

プロセス構造図は次のとおりです。ここに画像の説明を挿入します

構成プロセスは非常に単純ですが、各コンポーネントのロジックを理解するためには、多数の公式ドキュメントを参照する必要があります。この記事に含まれるドキュメントは次のように構成されています:
1. センサー データ エクスポート API ドキュメント リファレンス: https:/ /manual.sensorsdata.cn/sa/latest/zh_cn/tech_export_transfer-150668708.html
2. DataFrame 列タイプと ODPS SQL フィールド タイプのマッピング テーブル: https://help.aliyun.com/zh/maxcompute/user-guide/sequence #section-avk-4s4-cfb
3. 実行結果を MaxCompute テーブル ドキュメントとして保存します。リンク: https://help.aliyun.com/zh/maxcompute/user-guide/execution#section-jwh-1y4-cfb
4. ODPSエントリ関連のドキュメント リンク: https://help.aliyun.com/zh/maxcompute/user-guide/use-pyodps-in-dataworks
5. 実行結果を MaxCompute テーブル ドキュメント リンク: https://help.aliyun として保存します。 com/zh/maxcompute/user -guide/execution#section-jwh-1y4-cfb
6. センサー機能リファレンスドキュメントのリンク: https://manual.sensorsdata.cn/sa/latest/zh_cn/page-137920660.html
7.スケジュールパラメータ形式リファレンスドキュメント :https://help.aliyun.com/zh/dataworks/user-guide/supported-formats-of-scheduling-parameters
8. プロジェクト 'xxx' は保護されています 解決策 : https://help.aliyun .com/zh /maxcompute/user-guide/odps-0130013
9. センサー データ (センサー) データ ソース リファレンス ドキュメント: https://help.aliyun.com/zh/dataworks/user-guide/sensors-data-data-ソース
10. さまざまなタイプのスケジューリング ノードのパラメーターに関するドキュメント: https://help.aliyun.com/zh/dataworks/user-guide/configure-scheduling-parameters-for- Different-types-of-nodes

4. ハイライト - 落とし穴を避けるためのガイド

最終的な解決策を滑らかに仕上げた後、落とし穴を回避するためのガイドともいえるいくつかの豆知識について話しましょう。

最初の落とし穴: Alibaba Cloud は深センノードの Shence Data のみをサポートします

Dataworks ワークスペースに新しいデータ ソースを追加すると、Shence データを直接導入できることがわかります。
画像.png
Shence データ受信アドレスが存在する限り、要件は非常に簡単です。
画像.png
Sence データ受信アドレス: Sence インターフェイスでは、データ フュージョン列の下にあるデータ アクセス ガイドを通じてこの情報を取得できます。
画像.png
データ ソースを導入した後、新しいオフライン同期ノードを作成しましたが、データ ソースに Shence ロゴが見つからないことがわかりました。
画像.png
ヘルプ ドキュメントで Shence を検索すると、センサー データ データ ソースのバインドをサポートしているのは中国南部 1 (深セン) リージョンのみであることがわかりました。
参考ドキュメント:https://help.aliyun.com/zh/dataworks/user-guide/sensors-data-data-source
画像.png
Alibaba Cloudは、時には嬉しいこともあれば、時には不安になることもあり、常に「驚き」を与えてくれます。

2つ目のピット:シェンストークン

Shence を直接使用しても機能しない場合は、Shence データの API を取得し、他の方法でアクセスしてみてください。

前述したように、Shence のデータ フュージョン列にあるデータ アクセス ガイドでは、トークンを含むデータ受信アドレスをコピーできますが、それを取得すると、Shence API ドキュメントと結合して、開始しようとします。 postman でのリクエスト、失敗が返されました: アクセス権がありませんか?
センサー データ エクスポートのリファレンス: https://manual.sensorsdata.cn/sa/latest/zh_cn/tech_export_transfer-150668708.html
画像.png
いくつかの API ドキュメントを 1 つずつ試しましたが、結果は機能しませんでした。
使用されたトークンは正しいですが、権限がないため、アカウントに問題があるか、トークン自体に問題がある可能性があります。アカウントに問題がある場合は、各アカウントが独立したトークンを持っている可能性があります。 、その後、アカウントのアクセス許可にバインドされます。同じサブジェクトにトークンがある場合、それはトークンの問題です。
運用保守のクラスメートにテストを手伝ってもらいます。同じトークン、同じ結果です。それがトークンの問題です。
その後、Shence サービス スタッフに尋ねたところ、管理者を通じて API シークレット トークンを取得する必要があることがわかりました。
また、運用保守のクラスメートに API シークレット トークンを取得してもらい、ついにデータ リクエストが成功しました。

3 番目の落とし穴: Alibaba Cloud RestAPI は成功の半分に過ぎません。

Shence を直接使用しても機能しない場合は、Alibaba Cloud RestAPI を使用してみてください。
データワークス ワークスペースに新しいデータ ソースを追加し、RestAPI を通じて Shence インターフェイスを再導入します。
画像.png
今度はデータソースが見つかりました。
画像.png
しかし、次のステップでデータをプレビューすると、空のデータが表示されます。成功の半分だけのようで、残りの半分には結果がありません。
画像.png

ピット 4: PyODPS 参照パラメーター

SQL では参照パラメータが使用されます'${参数名}'が、PyODPS では使用されません。最初は'${参数名}'テストを使ってデータベースに入力しましたが、データは書き込まれませんでした。一時は、Alibaba Cloud の関連設定に何かが隠されているのではないかと疑ったのです。その後、方法を変更してさまざまな方法をテストし、レポートを開始しました。エラーレポートを注意深く調べた結果、私は徐々にいくつかの「トリック」を発見しました。それは、システムが私が何を与えているのかを理解していないためであると思われました。
最後に、print('${参数名}')パラメータを出力すると、結果がそのまま出力されました。
そのときになって初めて、この引用が間違っていることに気づきました。
正しい引用は次のとおりです**args['参数名']**
これは Tongyi Qianwen を通じて解決されました ~~~
画像.png
後で関連ドキュメントを見つけました: さまざまなタイプのスケジューリング ノード パラメーターの説明: https://help.aliyun.com/zh/dataworks/user-guide/configure-scheduling-parameters -for-さまざまな種類のノード

ピット 5: プロジェクト「for_analysis」は保護されています

エラー内容は以下の通りです。

odps.errors.ODPSError: ODPS-0130013: InstanceId: 2023112107222501384g
カタログ サービスが失敗しました、エラーコード: 50、エラー メッセージ: ODPS-0130013:認可例外 - 認可失敗 [4022]、{acs: に対する権限 'odps:Alter' がありません。 odps::projects/xxx/tables/xxxxxx}。プロジェクト「xxx」は保護されています。コンテキスト ID:343ac503-5031-af8-7b221256e4d5。—>ヒント: 現在のプロジェクト:xxx; 代表者:[email protected]; リソース acs:odps::projects/xxx/tables/xxxxxx に対する権限「odps:Alter」がありません

エラーの理由は、現在のプロジェクトでデータ保護ポリシーが有効になっており、外部プロジェクトによって開始されたデータ アクセスが許可されていないためです。

解決策は、このプロジェクトの所有者がデータ保護ポリシーをオフにすることです。

set ProjectProtection=false;

参考ドキュメント:https://help.aliyun.com/zh/maxcompute/user-guide/odps-0130013

もちろん、データ ウェアハウス アーキテクチャが複数のプロジェクトで構築されており、各プロジェクトが独自の責任を負っている場合は、プロジェクトの変更を検討し、操作が許可されているプロジェクトで操作を実行することもできます。

おすすめ

転載: blog.csdn.net/qq_45476428/article/details/134613570