# 黒い砂漠の取引所データのダンプ
## 概要
黒い砂漠の取引所のデータをダンプする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)
```