دریافت دیتای بورس با پایتون: از اسکرپینگ TSETMC تا API
برای دریافت دیتای بورس با پایتون دو راه دارید: خواندن مستقیم از TSETMC با اسکرپر خودتان، یا گرفتن دادهٔ آماده از یک API. با روشا، سابقهٔ روزانهٔ تعدیلشدهٔ یک نماد با یک درخواست HTTP و حدود ۲۰ خط کد در یک DataFrame قرار میگیرد.
این نوشته برای کسی است که اسکرپر دارد یا میخواهد بنویسد. اول میگوییم نگهداری اسکرپر چه کارهایی دارد، بعد همان کار را با API انجام میدهیم.
نگهداری اسکرپر TSETMC چه کارهایی دارد؟
نوشتن اولین نسخهٔ اسکرپر یک بعدازظهر وقت میگیرد. کار اصلی پس از آن شروع میشود:
- تغییر منبع. اسکرپر به نشانیها و قالب پاسخ سایت منبع وابسته است. هر تغییر در آنها کد شما را از کار میاندازد و باید همان روز اصلاح شود.
- یافتن
insCode. TSETMC هر نماد را با یک کد عددی بلند میشناسد. نمادها تغییر نام میدهند و یک نماد ممکن است چند کد قدیمی و جدید داشته باشد. - تعدیل قیمت. سابقهٔ خام در افزایش سرمایه و سود نقدی میپرد. باید رویدادها را جدا بگیرید و ضریبها را خودتان اعمال کنید.
- یکسانسازی متن. نامها با حروف «ی» و «ک» عربی میآیند و جستوجو با شکل فارسی این دو حرف چیزی پیدا نمیکند.
- تاریخ و واحد. تاریخها عدد هشترقمی هستند، ساعتها عدد ششرقمی و قیمتها ریال.
- رفتار مؤدبانه با منبع. باید سرعت درخواستها را محدود کنید، خطاها را دوباره امتحان کنید و در ساعت بازار فشار نیاورید.
- صندوقها. دادهٔ NAV از منبع دیگری (فیپیران) میآید و اسکرپر دوم میخواهد.
هیچکدام از اینها سخت نیست. اما همه با هم یعنی یک سرویس کوچک که کسی باید همیشه مراقبش باشد.
کتابخانههای متنباز چطور؟
کتابخانههای متنباز پایتون مثل pytse-client و finpy-tse سالهاست کار با دادهٔ بازار ایران را برای جامعهٔ توسعهدهندگان ساده کردهاند. برای یادگیری، پروژهٔ شخصی و تحلیل یکباره انتخاب خوبی هستند و مستندات هر کدام در مخزن خودشان است.
روشا کار دیگری میکند. روشا یک سرویس میزبانیشده است: داده را خودمان هر روز جمع میکنیم، بررسی میکنیم و در پایگاه داده نگه میداریم. شما از سرور ما میخوانید، نه از سایت منبع. اگر منبع تغییر کند، اصلاح آن کار ماست و قرارداد /v1 برای شما ثابت میماند.
همان کار با API روشا
یک کلید رایگان بگیرید و آن را در ROSHA_KEY بگذارید. بستهها را نصب کنید:
pip install requests pandasexport ROSHA_KEY="rosha_xxxxxxxx_your-secret"کد زیر نماد را جستوجو میکند، کل سابقهٔ روزانهٔ تعدیلشده را میگیرد و به DataFrame تبدیل میکند:
import osimport time
import pandas as pdimport requests
BASE = "https://poolick.ir"HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}
def get(path, **kwargs): """GET with one retry on 429, waiting as long as Retry-After says.""" for _ in range(2): r = requests.get(BASE + path, headers=HEADERS, timeout=30, **kwargs) if r.status_code != 429: break time.sleep(int(r.headers.get("Retry-After", "1"))) r.raise_for_status() return r.json()
def daily(ins_code, adjusted=True): """Full daily history of one instrument as a DataFrame indexed by date.""" data = get( f"/v1/instruments/{ins_code}/daily", params={"adjusted": str(adjusted).lower(), "limit": 5000}, ) df = pd.DataFrame(data["rows"]).set_index("d") df.index = pd.to_datetime(df.index) return df
match = get("/v1/instruments", params={"q": "فولاد", "limit": 1})[0]df = daily(match["ins_code"])print(match["ticker"], match["name"], match["ins_code"])print(df[["close", "volume"]].tail(3))خروجی واقعی در ۲۳ آبان ۱۴۰۵:
فولاد فولاد مبارکه اصفهان 46348559193224090 close volumed2026-11-09 4670.0 8.969032e+082026-11-10 4810.0 1.031670e+092026-11-11 4950.0 1.142422e+09هر بخش اسکرپر به چه چیزی تبدیل میشود؟
| کار در اسکرپر | در روشا |
|---|---|
یافتن insCode نماد |
GET /v1/instruments?q= یا خود نماد فارسی بهجای ref |
| سابقهٔ قیمت روزانه | GET /v1/instruments/{ref}/daily |
| تعدیل افزایش سرمایه و سود نقدی | adjusted=true |
| فهرست رویدادهای تعدیل | GET /v1/instruments/{ref}/adjustments |
| حقیقی و حقوقی | GET /v1/instruments/{ref}/client-type |
| سابقهٔ شاخص | GET /v1/indices/{ref}/daily |
| روزهای معاملاتی | GET /v1/calendar?start=&end= |
| NAV صندوق از فیپیران | GET /v1/funds/{ref}/history |
| ریز معاملات و کندل درونروزی | GET /v1/instruments/{ref}/trades و /bars (پلن دارای intraday) |
مرجع کامل در مستندات API است.
چند قرارداد که باید بدانید
ins_codeرشته است. این کد از بازهٔ عدد صحیح امن جاوااسکریپت بزرگتر است. در pandas هم آن را متن نگه دارید، وگرنه در تبدیل بهfloatرقم از دست میدهد.- ردیفها از قدیم به جدید هستند.
limitجدیدترین N ردیف بازه را نگه میدارد. پسlimit=1آخرین روز است. - هر درخواست حداکثر ۵۰۰۰ ردیف. برای بازهٔ بلندتر با تاریخ صفحهبندی کنید:
endرا روز پیش از قدیمیترین ردیف بگذارید. - تاریخ ورودی آزاد است.
2026-11-11،20261111و شمسی1405-08-20هر سه پذیرفته میشوند. تاریخ خروجی میلادی ISO است و ردیفهای روزانهٔ سهامjdateشمسی هم دارند. - همهچیز ریال است. برای تومان بر ۱۰ تقسیم کنید.
closeقیمت پایانی وlastقیمت آخرین معامله است. این دو معمولاً برابر نیستند.- روز بدون معامله.
open،highوlowتهی هستند وvolumeصفر است.
جزئیات در قراردادها و خطاها آمده است.
چند نماد پشت سر هم
برای چند نماد، همان تابع را در حلقه صدا بزنید. تابع get در پاسخ 429 به اندازهٔ Retry-After صبر میکند، پس حلقه از سقف دقیقهای پلن بیرون نمیزند. این تکه ادامهٔ کد بالاست:
tickers = ["فولاد", "فملی", "خودرو"]closes = {}for ticker in tickers: match = get("/v1/instruments", params={"q": ticker, "limit": 1})[0] closes[match["ticker"]] = daily(match["ins_code"])["close"]
panel = pd.DataFrame(closes).dropna()print(panel.tail(3))جستوجو با زیررشته کار میکند و اولین نتیجه همیشه نماد دلخواه شما نیست. در کد تولیدی، ins_code را یک بار پیدا و ذخیره کنید و بعد فقط با همان کار کنید.
چه چیزی را روشا ندارد؟
صورتهای مالی و اطلاعیههای کدال، دادهٔ بنیادی شرکتها و قیمت ارز و طلا در روشا نیست. ریز معاملات فقط برای روزهایی هست که ثبت شدهاند. اگر اسکرپر شما اینها را میخواند، آن بخش را نگه دارید.
گام بعدی
- برای نگهداری داده در پایگاه دادهٔ خودتان: همگامسازی روزانه.
- راهنمای گامبهگام مهاجرت: مهاجرت از اسکرپینگ.
- نمونههای بیشتر پایتون: مستندات پایتون.
- سقفهای هر پلن: محدودیتها و قیمتها.