# 黒い砂漠の取引所データのダンプ ## 概要 黒い砂漠の取引所のデータをダンプするPythonスクリプトを書きました。4545 Python2ってところがダサいと思いませんか。 ほならね自分でやってみろって話ですよ。 わたしはそういいたい ## 使い方 python2を導入して、次に記載のモジュールを導入 (urllib1,2は多分標準、多分) ### Jsonを綺麗に表示するモジュール pip install jsbeautifier まぁいらないです。表示用 ### 実行 事前にカレントディレクトリにディレクトリ名「blackdesert_dump」を作成する $ python main.py $ cookie: <ここにブラウザから取得したCookieを乗せる> $ verification_token: <ここにブラウザから取得したverification_tokenを乗せる> ### コツ verification_tokenはCSRF対策のtokenなので、index(/home/list/hot)にアクセスするたびに変更されます。 各ブラウザの開発者ツールからAPIのリクエスト内容を取得してverification_tokenを取得してください。 ## 課題 oauthのログインからセッション取りたい。 category,CSRF_tokenのスクレイピングしたい。 ## ソースコード ```python:main.py #!/usr/bin/env python2 # -*- coding: utf-8 -*- from __future__ import unicode_literals from __future__ import print_function import urllib2 import urllib import json from collections import OrderedDict import jsbeautifier # pip install jsbeautifier header = """Accept: */* Accept-Encoding: deflate, br Accept-Language: ja,en-US;q=0.9,en;q=0.8 Connection: keep-alive Content-Length: <LENGTH> Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Cookie: <COOKIE> Host: trade.jp.playblackdesert.com Origin: https://trade.jp.playblackdesert.com Referer: https://trade.jp.playblackdesert.com/Home/list/35-1 Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 X-Requested-With: XMLHttpRequest""" category = { 'メイン武器': { 'data-main': 1, 'data-sub-count': 15, 'data-sub-list': { '1': 'ソード', '2': 'ロングボウ', '3': '護符', '4': 'アックス', '5': '小剣', '6': '刀剣', '7': 'スタッフ', '8': '太刀', '9': '手甲', '10': '半月錘', '11': 'クロスボウ', '12': 'フローラン', '13': 'バトルアックス', '14': '砂曲刀', '15': 'モーニングスター' } }, '補助武器': { 'data-main': 5, 'data-sub-count': 14, 'data-sub-list': { '1': 'シールド', '2': 'ダガー', '3': 'タリスマン', '4': '組み糸飾り', '5': '装身具', '6': '角弓', '7': '苦無', '8': '手裏剣', '9': '腕甲', '10': '古剣', '11': 'マギアグローブ', '12': 'ビツアリ', '13': '禁戒', '14': 'クラトゥム' } }, '覚醒武器': { 'data-main': 10, 'data-sub-count': 21, 'data-sub-list': { '1': '大剣', '2': 'デスサイズ', '3': 'バスターガントレ', '4': '精霊剣', '5': '天聖棍', '6': 'ランス', '7': '武神刀', '8': '花月槍', '9': '修羅刀', '10': '月輪刀', '11': 'アドソウル', '12': 'ゴッドソウル', '13': 'ベディアント', '14': '闘神甲', '15': '蒼龍甲', '16': '血柳刃', '17': 'グランドボウ', '19': 'ヨルドゥン', '20': '誅殺刀', '21': 'スティング' } }, '防具': { 'data-main': 15, 'data-sub-count': 6, 'data-sub-list': { '1': '兜', '2': '鎧', '3': '手袋', '4': '靴', '5': '機能服', '6': '製作服' } }, 'アクセサリー': { 'data-main': 20, 'data-sub-count': 4, 'data-sub-list': { '1': 'リング', '2': 'ネックレス', '3': 'イヤリング', '4': 'ベルト' } }, '材料': { 'data-main': 25, 'data-sub-count': 8, 'data-sub-list': { '1': '鉱石/宝石', '2': '草木', '3': '種子/実', '4': '皮', '5': '血', '6': '肉', '7': '魚介類', '8': 'その他材料' } }, '強化/改良': { 'data-main': 30, 'data-sub-count': 2, 'data-sub-list': { '1': 'ブラックストーン', '2': '改良' } }, '消耗品': { 'data-main': 35, 'data-sub-count': 8, 'data-sub-list': { '1': '攻撃型エリクサー', '2': '防御型エリクサー', '3': '機能型エリクサー', '4': '料理', '5': 'ポーション', '6': '攻城関連', '7': '調合アイテム', '8': 'その他消耗品' } }, '生活道具': { 'data-main': 40, 'data-sub-count': 10, 'data-sub-list': { '1': '伐採斧', '2': '樹液採取', '3': '屠殺用ナイフ', '4': 'ツルハシ', '5': '鍬', '6': '皮用ナイフ', '7': '釣り道具', '8': '火縄銃', '9': '錬金/料理', '10': 'その他道具' } }, '錬金石': { 'data-main': 45, 'data-sub-count': 4, 'data-sub-list': { '1': '破壊の錬金石', '2': '守護の錬金石', '3': '生命の錬金石', '4': '精霊石' } }, '魔力水晶': { 'data-main': 50, 'data-sub-count': 7, 'data-sub-list': { '1': 'メイン武器', '2': '補助武器', '8': '覚醒武器', '3': '兜', '4': '鎧', '5': '手袋', '6': '靴', '7': '全部位' } }, 'パール商品': { 'data-main': 55, 'data-sub-count': 8, 'data-sub-list': { '1': '男性衣装セット', '2': '女性衣装セット', '3': '男性衣装単品', '4': '女性衣装単品', '5': '共用衣装セット', '6': '便利/機能', '7': '搭乗物関連', '8': 'ペット' } }, '染色剤': { 'data-main': 60, 'data-sub-count': 8, 'data-sub-list': { '1': '基本', '2': 'オルビア製', '3': 'ベリア製', '4': 'ハイデル製', '5': 'ケプラン製', '6': 'カルフェオン製', '7': 'メディア製', '8': 'バレンシア製' } }, '動物': { 'data-main': 65, 'data-sub-count': 13, 'data-sub-list': { '1': '登録証', '2': '餌', '3': '馬面', '4': '馬鎧', '5': '鞍', '6': '鐙', '7': '蹄鉄', '9': '[象] 革鐙', '10': '[象] 象甲', '11': '[象] 象面', '12': '[象] 鞍', '13': '駿馬訓練' } }, '船舶': { 'data-main': 70, 'data-sub-count': 9, 'data-sub-list': { '1': '登録証', '2': '荷台', '3': '船首', '4': '飾り', '5': 'トーテム', '6': '船首像', '7': '装甲', '8': '艦砲', '9': '帆' } }, '馬車': { 'data-main': 75, 'data-sub-count': 6, 'data-sub-list': { '1': '登録証', '2': '車輪', '3': 'カバー', '4': '旗', '5': '天幕', '6': 'ランプ' } }, '家具': { 'data-main': 80, 'data-sub-count': 9, 'data-sub-list': { '1': 'ベッド', '2': 'テーブル', '3': 'ワードローブ/本棚', '4': 'ソファ/椅子', '5': 'シャンデリア', '6': '床/カーペット', '7': '壁/カーテン', '8': '飾り', '9': 'その他' } } } def get_data(header, url, param): try: param = urllib.urlencode(param) header = header.replace("<LENGTH>", str(len(param))) header_parsed = {} for h in header.split("\n"): h_split = h.split(": ") header_parsed.update({h_split[0]:h_split[1]}) req = urllib2.Request(url, headers=header_parsed, data=param) res = urllib2.urlopen(req) body = res.read() except: import traceback traceback.print_exc() body = '' return body.decode('utf-8') """ * GetWorldMarketSubList index(/home/list/hot)から取得したCategoryから情報を取得するAPI * * mainCategory "/Home/list/hot"から取得したdata-mainを指定する。 * * subCategory "/Home/list/hot"から取得したdata-subを指定する。 """ GetWorldMakertList_param = { '__RequestVerificationToken' : '', 'mainCategory' : '', 'subCategory' : '' } def GetWorldMarketList(header, param): url = 'https://trade.jp.playblackdesert.com/Home/GetWorldMarketList' return get_data(header, url, param) """ * GetWorldMarketSubList itemidを指定して、hotな価格や登録数を取得するAPI * * mainKey ここにGetWorldMakertListから取得したitemidを指定する。 * * usingCleint ブラウザ経由の場合は0を指定 """ GetWorldMarketSubList_param = { '__RequestVerificationToken' : '', 'mainKey' : '', 'usingCleint' : '' } def GetWorldMarketSubList(header, param): url = 'https://trade.jp.playblackdesert.com/Home/GetWorldMarketSubList' return get_data(header, url, param) def main(): global header,category cookie = raw_input("cookie:") verification_token = raw_input("verification_token:") header = header.replace("<COOKIE>", cookie) for Category_name in category: Category = category[Category_name] mainCategory = Category['data-main'] subCategoryCount = Category['data-sub-count'] save_format = Category for subCategory in range(1, subCategoryCount + 1): if str(subCategory) in Category['data-sub-list']: param = GetWorldMakertList_param param['__RequestVerificationToken'] = verification_token param['mainCategory'] = mainCategory param['subCategory'] = subCategory Responce_data = jsbeautifier.beautify(GetWorldMarketList(header, param)) Responce_data_json = json.loads(Responce_data, 'utf-8', object_pairs_hook=OrderedDict) subCategoryName = save_format['data-sub-list'][str(subCategory)] save_format['data-sub-list'][str(subCategory)] = {'data-sub-name': subCategoryName, 'data':Responce_data_json} open("./blackdesert_dump/" + str(mainCategory) + '_' + str(subCategoryCount) + ".json", 'wb').write(jsbeautifier.beautify(json.dumps(save_format, ensure_ascii=False)).encode('utf-8')) """ レスポンスのサンプル { "marketList": [{ "mainKey": 9746, "sumCount": 175, "totalSumCount": 82175, "name": "深淵の真鍮の塊", "grade": 3, "minPrice": 510000 }], "resultCode": 0, "resultMsg": "" } """ """marketList = json.loads(Responce_data) for item in marketList['marketList']: param = GetWorldMarketSubList_param param['__RequestVerificationToken'] = verification_token param['mainKey'] = item['mainKey'] param['usingCleint'] = 0 Responce_data = jsbeautifier.beautify(GetWorldMarketSubList(header, param)) break #爆発しないようにfor文を止めてます。""" """ レスポンスのサンプル { "detailList": [{ "pricePerOne": 690, "totalTradeCount": 499816778, "keyType": 0, "mainKey": 4001, "subKey": 0, "count": 383, "name": "鉄鉱石", "grade": 0, "mainCategory": 25, "subCategory": 1, "chooseKey": 0 }], "resultCode": 0, "resultMsg": "" } """ if __name__ == "__main__": main() ``` 総合取引所のdump結果 2021/03/07 https://drive.google.com/drive/folders/1PV8oGunybhF1hLl_h_1UyHaDbEjfupR-?usp=sharing ### 特定のjsonデータから名前と価格を抽出するスクリプト #### 扱うデータ 取引所のJSONデータ ```python:main.py #!/usr/bin/env python2 # -*- coding: utf-8 -*- import json from collections import OrderedDict ingredients_data = json.loads(open('25_8.json', 'rb').read(), 'utf-8', object_pairs_hook=OrderedDict)["data-sub-list"] cooking_data = json.loads(open('35_8.json', 'rb').read(), 'utf-8', object_pairs_hook=OrderedDict)["data-sub-list"] creatures_data = json.loads(open('65_13.json', 'rb').read(), 'utf-8', object_pairs_hook=OrderedDict)["data-sub-list"] output_file_data = '' for ingredients_count in ingredients_data: marketList = ingredients_data[ingredients_count]['data']['marketList'] data_sub_name = ingredients_data[ingredients_count]['data-sub-name'].encode('utf-8') if(data_sub_name == "種子/実" or data_sub_name == "草木" or data_sub_name == "血" or data_sub_name == "魚介類" or data_sub_name == "肉" or data_sub_name == "その他材料"): for data in marketList: output_file_data += "%s\t%s\n" % (data['name'], data['minPrice']) for cooking_count in cooking_data: marketList= cooking_data[cooking_count]['data']['marketList'] data_sub_name = cooking_data[cooking_count]['data-sub-name'].encode('utf-8') if(data_sub_name == "料理"or data_sub_name == "ポーション"): for data in marketList: output_file_data += "%s\t%s\n" % (data['name'], data['minPrice']) for creatures_count in creatures_data: marketList= creatures_data[creatures_count]['data']['marketList'] data_sub_name = creatures_data[creatures_count]['data-sub-name'].encode('utf-8') if(data_sub_name == "餌"): for data in marketList: output_file_data += "%s\t%s\n" % (data['name'], data['minPrice']) open('output.txt', 'wb').write(output_file_data.encode('utf-8')) ``` ### wikiの料理データをシート用にパースするスクリプト #### 扱うデータの例 https://docs.google.com/document/d/1slYqilJgBe9lnQE6vrOz8lHaawuTiXOHiXQvOoHxyls/edit?usp=sharing #### シート https://docs.google.com/spreadsheets/d/1hrN9_2aoz4_04oV_2Wk7IV-HNyF2YQhXIM8PTAmrQbk/edit #### 元データ https://blackdesert.swiki.jp/index.php?%E6%96%99%E7%90%86 ```python:parser.py #!/usr/bin/env python2 # -*- coding: utf-8 -*- import copy import re format1 = ["料理名\t", "=offset(indirect(address(row(),column())), 8, 2)\t", "\t", "\t", "\t", "\n"]#料理名 format2 = ["\t", "単価\t", "個数\t", "金額\t", "\t", "上位料理or代用品を含めた材料\t", "\n"]#タイトル format3 = ["\t", "=if(isna(vlookup(indirect(address(row(),column()-1,4)),I:J,2,false)),0,vlookup(indirect(address(row(),column()-1,4)),I:J,2,false))\t", "0\t", "=offset(indirect(address(row(),column() - 2)), 0, 0) * offset(indirect(address(row(),column() - 1)), 0, 0)\t", "\t", "\t", "\n"]#材料1 format4 = ["\t", "=if(isna(vlookup(indirect(address(row(),column()-1,4)),I:J,2,false)),0,vlookup(indirect(address(row(),column()-1,4)),I:J,2,false))\t", "0\t", "=offset(indirect(address(row(),column() - 2)), 0, 0) * offset(indirect(address(row(),column() - 1)), 0, 0)\t", "\t", "\n"]#材料2 format5 = ["\t", "=if(isna(vlookup(indirect(address(row(),column()-1,4)),I:J,2,false)),0,vlookup(indirect(address(row(),column()-1,4)),I:J,2,false))\t", "0\t", "=offset(indirect(address(row(),column() - 2)), 0, 0) * offset(indirect(address(row(),column() - 1)), 0, 0)\t", "\t", "\n"]#材料3 format6 = ["\t", "=if(isna(vlookup(indirect(address(row(),column()-1,4)),I:J,2,false)),0,vlookup(indirect(address(row(),column()-1,4)),I:J,2,false))\t", "0\t", "=offset(indirect(address(row(),column() - 2)), 0, 0) * offset(indirect(address(row(),column() - 1)), 0, 0)\t", "\t", "\n"]#材料4 format7 = ["\t", "=if(isna(vlookup(indirect(address(row(),column()-1,4)),I:J,2,false)),0,vlookup(indirect(address(row(),column()-1,4)),I:J,2,false))\t", "0\t", "=offset(indirect(address(row(),column() - 2)), 0, 0) * offset(indirect(address(row(),column() - 1)), 0, 0)\t", "\t", "\n"]#材料5 format8 = ["\t", "\t", "費用計\t", "=sum(indirect(address(row()-5,column())):indirect(address(row()-1,column())))\t", "\t", "\n"] format9 = ["\t", "\t", "一個当たり\t", "=offset(indirect(address(row(),column())), -1, 0) / 2.5\t", "\t", "\n"] format10 = ["\n"] def del_newline(data):#改行を消す data = data.replace("\r", "") data = data.replace("\n", "") return data def del_brackets(data):#括弧以降を消す i = data.find("(") if(i != -1): data = data[:i] return data """ ■観測した誤りリスト (肉類) (海産物) (魚) (キノコ類) (野菜) (ロブスター) (食用蜂蜜) (豚.+の血) (穀類) (穀物粉) (フルーツ類|果物|果実) (ワニ肉類) (花類) """ def filter_num(data):#曖昧な区切りを統一する data = data.replace(":", ":") data = data.replace("x", ":") #複数提案があった場合に一つに調整する if(data.count(":") == 2): result = re.findall(".*?:[0-9]+", data) if(result): data = result[0] return data def avoid_obscure(data):#曖昧表現を訂正する obscure_list = [ ["(肉類)", "肉類"], ["(海産物)", "乾燥海産物類"], ["(魚)", "乾燥魚類"], ["(野菜)", "野菜類"], ["(ロブスター)", "乾燥ロブスター"], ["(食用蜂蜜)", "食用蜂蜜"], ["(豚.*?の血)", "豚の血類"], ["(穀類)", "穀物類"], ["(穀物粉)", "穀物粉類"], ["(フルーツ類|果物|果実)", "フルーツ類"], ["(ワニ肉類)", "ワニの肉"], ["(花類)", "花類"] ] data = del_brackets(data) for obscure in obscure_list: result = re.findall(obscure[0], data) if(result): data = obscure[1] return data def if_dry(data, name):#乾燥魚類なら要求個数をちゃんと計算し直す data = del_brackets(data) if name == "乾燥魚類" or name == "海産物類" or name == "乾燥ロブスター": data = "2" return data data = open('data.txt', 'rb').read() out_file_data = '' for data_list in zip(*[iter(data.split("\t"))]*6): #cooking_name = data_list[1] + ("(%s → 料理箱等級:%s)" % (del_newline(data_list[2]), del_newline(data_list[0])))#料理名(作成可能熟練度:num) cooking_name = data_list[1] ingredients = data_list[3].split("\r\n") if(len(ingredients) < 5):#padding for i in xrange(5 - len(ingredients)): ingredients.append("") top_ingredient = data_list[4] effect = data_list[5] line1 = copy.deepcopy(format1) line1[0] = cooking_name + "\t"#料理名 line2 = copy.deepcopy(format2) i = 0#材料のカウント用 line3 = copy.deepcopy(format3)#材料1 ingredient_split = filter_num(ingredients[i]).split(":") if(len(ingredient_split) == 2): line3[0] = avoid_obscure(ingredient_split[0]) + "\t" line3[2] = if_dry(ingredient_split[1], line3[0]) + "\t" if(top_ingredient):#上位料理or代用品を含めた材料 line3[5] = del_newline(top_ingredient) + "\t" i += 1 line4 = copy.deepcopy(format4)#材料2 ingredient_split = filter_num(ingredients[i]).split(":") if(len(ingredient_split) == 2): line4[0] = avoid_obscure(ingredient_split[0]) + "\t" line4[2] = if_dry(ingredient_split[1], line4[0]) + "\t" i += 1 line5 = copy.deepcopy(format5)#材料3 ingredient_split = filter_num(ingredients[i]).split(":") if(len(ingredient_split) == 2): line5[0] = avoid_obscure(ingredient_split[0]) + "\t" line5[2] = if_dry(ingredient_split[1], line5[0]) + "\t" i += 1 line6 = copy.deepcopy(format6)#材料4 ingredient_split = filter_num(ingredients[i]).split(":") if(len(ingredient_split) == 2): line6[0] = avoid_obscure(ingredient_split[0]) + "\t" line6[2] = if_dry(ingredient_split[1], line6[0]) + "\t" i += 1 line7 = copy.deepcopy(format7)#材料5 ingredient_split = filter_num(ingredients[i]).split(":") if(len(ingredient_split) == 2): line7[0] = avoid_obscure(ingredient_split[0]) + "\t" line7[2] = if_dry(ingredient_split[1], line7[0]) + "\t" i += 1 line8 = copy.deepcopy(format8) line9 = copy.deepcopy(format9) line10 = copy.deepcopy(format10) output = "".join(line1) output += "".join(line2) output += "".join(line3) output += "".join(line4) output += "".join(line5) output += "".join(line6) output += "".join(line7) output += "".join(line8) output += "".join(line9) output += "".join(line10) out_file_data += output open('output.txt', 'wb').write(out_file_data) ```