diff --git a/README.md b/README.md index 5884762e..66fec986 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[English](README.md) | [简体中文](README.zh-CN.md) # Just One API - Python SDK Official Python SDK for accessing [Just One API](https://justoneapi.com) — a unified data service platform offering structured data from Social, E-commerce platforms such as Xiaohongshu, Taobao, Douyin, Kuaishou, Bilibili, and Weibo. @@ -57,7 +58,7 @@ Each API method returns one or more of the following values: ## 🔐 Authentication All API requests require a valid API token. -👉 [Register](https://justoneapi.com/register) +👉 [Register](https://user.justoneapi.com/sign-up) --- @@ -91,4 +92,4 @@ If you have any questions, feedback, or partnership inquiries: ## 🪪 License This project is licensed under the MIT License. -See the [LICENSE](./LICENSE) file for details. \ No newline at end of file +See the [LICENSE](./LICENSE) file for details. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 00000000..af608b9e --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,94 @@ +[English](README.md) | [简体中文](README.zh-CN.md) + +# Just One API - Python SDK + +官方 Python SDK,用于访问 [Just One API](https://justoneapi.com/zh/) —— 一个统一的数据服务平台,提供来自小红书、淘宝、抖音、快手、哔哩哔哩、微博等社交、电商平台的结构化数据。 + +该 SDK 简化了 API 调用与签名流程,让开发者能够以最少的配置快速获取各平台数据。 + +--- + +## 🚀 安装 + +通过 PyPI 安装: + +```bash +pip install justoneapi +``` + +--- + +## 🛠 快速开始 + +```python +from justoneapi.client import JustOneAPIClient + +# 默认使用 “cn”(中国大陆)环境 +client = JustOneAPIClient(token="your_token") +# 若您位于中国大陆以外地区,访问速度较慢,可切换至 “global” 环境以获得更佳性能: +# client = JustOneAPIClient(token="your_token", env="global") + +# 示例:获取抖音视频详情 +result, data, message = client.douyin.get_video_detail_v2(video_id="7428906452091145483") +print(result) +print(data) +print(message) + +# 示例:抖音视频搜索 +result, data, message, has_next_page = client.douyin.search_video_v4(keyword="deepseek", sort_type="_0", publish_time="_0", duration="_0", page=1) +print(result) +print(data) +print(message) +print(has_next_page) +``` + +### 📦 返回值说明 + +每个 API 方法会返回以下一个或多个值: + +| 变量名 | 类型 | 说明 | +|--------|------|------| +| `result` | `bool` | 请求是否成功。`True` 表示成功,`False` 表示失败。 | +| `data` | `dict` / `list` | 实际返回的数据,结构因接口而异。 | +| `message` | `str` | 服务器返回的信息;当请求失败时包含错误说明。 | +| `has_next_page` | `bool` | 仅分页接口返回,表示是否还有下一页数据。 | + +--- + +## 🔐 身份认证 + +所有 API 请求均需携带有效的 API Token。 +👉 [注册获取 Token](https://user.justoneapi.com/zh/sign-up) + +--- + +## 📚 文档中心 + +👉 完整 API 文档:[接口文档](https://doc.justoneapi.com) + +内容包括: +- 请求参数 +- 返回字段 +- 错误码说明 + +--- + +## 🏠 官方网站 + +👉 [官方网站](https://justoneapi.com/zh/) + +了解更多项目介绍、数据来源及商业集成方案。 + +--- + +## 📬 联系我们 + +如有任何问题、反馈或合作意向,欢迎联系: +👉 [联系我们](https://justoneapi.com/zh/contact) + +--- + +## 🪪 许可证 + +本项目基于 MIT 开源许可证发布。 +详情参见 [LICENSE](./LICENSE) 文件。 diff --git a/justoneapi/__init__.py b/justoneapi/__init__.py index 55bf7600..c85ce965 100644 --- a/justoneapi/__init__.py +++ b/justoneapi/__init__.py @@ -1,2 +1,2 @@ -__version__ = "1.9.0" +__version__ = "1.15.2" diff --git a/justoneapi/apis/jd.py b/justoneapi/apis/jd.py new file mode 100644 index 00000000..9c62e68a --- /dev/null +++ b/justoneapi/apis/jd.py @@ -0,0 +1,16 @@ +from justoneapi.apis import request_util + + +class JdAPI: + def __init__(self, token: str, base_url: str): + self.token = token + self.base_url = base_url + + def get_item_detail_v1(self, item_id: str): + url = f"{self.base_url}/api/jd/get-item-detail/v1" + params = { + "token": self.token, + "itemId": item_id, + } + return request_util.get_request(url, params) + diff --git a/justoneapi/apis/search.py b/justoneapi/apis/search.py new file mode 100644 index 00000000..58d8e8d0 --- /dev/null +++ b/justoneapi/apis/search.py @@ -0,0 +1,33 @@ +from justoneapi.apis import request_util +from justoneapi.log import logger + + +class SearchAPI: + def __init__(self, token: str, base_url: str): + self.token = token + self.base_url = base_url + + def search_v1(self, keyword: str = None, source: str = None, start: str = None, end: str = None, next_cursor: str = None): + url = f"{self.base_url}/api/search/v1" + params = { + "token": self.token, + } + if keyword: + params["keyword"] = keyword + if source: + params["source"] = source + if start: + params["start"] = start + if end: + params["end"] = end + if next_cursor: + params["nextCursor"] = next_cursor + + has_next_page = False + result, data, message = request_util.get_request_page(url, params) + try: + if data.get("nextCursor"): + has_next_page = True + except Exception as e: + logger.warning(f"Pagination parse error at {url}. Contact us to fix it.") + return result, data, message, has_next_page diff --git a/justoneapi/apis/taobao.py b/justoneapi/apis/taobao.py index bd7bd0f8..84d47159 100644 --- a/justoneapi/apis/taobao.py +++ b/justoneapi/apis/taobao.py @@ -55,6 +55,41 @@ def get_item_detail_v7(self, item_id: str): } return request_util.get_request(url, params) + def get_item_detail_v8(self, item_id: str): + url = f"{self.base_url}/api/taobao/get-item-detail/v8" + params = { + "token": self.token, + "itemId": item_id, + } + return request_util.get_request(url, params) + + def get_item_detail_v9(self, item_id: str): + url = f"{self.base_url}/api/taobao/get-item-detail/v9" + params = { + "token": self.token, + "itemId": item_id, + } + return request_util.get_request(url, params) + + def get_item_comment_v1(self, item_id: str, page: int = 1, order_type: str = "feedbackdate"): + url = f"{self.base_url}/api/taobao/get-item-comment/v1" + params = { + "token": self.token, + "itemId": item_id, + "page": page, + "orderType": order_type, + } + + has_next_page = False + result, data, message = request_util.get_request_page(url, params) + try: + if data: + if data.get("hasNext") == "true": + has_next_page = True + except Exception as e: + logger.warning(f"Pagination parse error at {url}. Contact us to fix it.") + return result, data, message, has_next_page + def get_item_comment_v6(self, item_id: str, page: int, order_type: str = None): url = f"{self.base_url}/api/taobao/get-item-comment/v6" params = { @@ -113,12 +148,11 @@ def get_social_feed_v1(self, item_id: str, page: int): logger.warning(f"Pagination parse error at {url}. Contact us to fix it.") return result, data, message, has_next_page - def get_shop_item_list_v1(self, user_id: str, shop_id: str, page: int, sort: str = None): + def get_shop_item_list_v1(self, user_id: str, shop_id: str = None, page: int = 1, sort: str = None): url = f"{self.base_url}/api/taobao/get-shop-item-list/v1" params = { "token": self.token, "userId": user_id, - "shopId": shop_id, "page": page, } if sort: diff --git a/justoneapi/apis/tiktok.py b/justoneapi/apis/tiktok.py new file mode 100644 index 00000000..4e5175af --- /dev/null +++ b/justoneapi/apis/tiktok.py @@ -0,0 +1,83 @@ +from justoneapi.apis import request_util + + +class TikTokAPI: + def __init__(self, token: str, base_url: str): + self.token = token + self.base_url = base_url + + def get_user_post_v1(self, sec_uid: str, cursor: str, sort: str = None): + url = f"{self.base_url}/api/tiktok/get-user-post/v1" + params = { + "token": self.token, + "secUid": sec_uid, + "cursor": cursor, + } + if sort: + params["sort"] = sort + return request_util.get_request(url, params) + + def get_post_detail_v1(self, post_id: str): + url = f"{self.base_url}/api/tiktok/get-post-detail/v1" + params = { + "token": self.token, + "postId": post_id, + } + return request_util.get_request(url, params) + + def get_user_detail_v1(self, unique_id: str = None, sec_uid: str = None): + url = f"{self.base_url}/api/tiktok/get-user-detail/v1" + params = { + "token": self.token, + } + if unique_id: + params["uniqueId"] = unique_id + if sec_uid: + params["secUid"] = sec_uid + return request_util.get_request(url, params) + + def get_post_comment_v1(self, aweme_id, cursor: str = None): + url = f"{self.base_url}/api/tiktok/get-post-comment/v1" + params = { + "token": self.token, + "awemeId": aweme_id, + } + if cursor: + params["cursor"] = cursor + return request_util.get_request(url, params) + + def get_post_sub_comment_v1(self, aweme_id, comment_id: str, cursor: str = None): + url = f"{self.base_url}/api/tiktok/get-post-sub-comment/v1" + params = { + "token": self.token, + "awemeId": aweme_id, + "commentId": comment_id, + } + if cursor: + params["cursor"] = cursor + return request_util.get_request(url, params) + + def search_user_v1(self, keyword, cursor: str = None, search_id: str = None): + url = f"{self.base_url}/api/tiktok/search-user/v1" + params = { + "token": self.token, + "keyword": keyword, + } + if cursor: + params["cursor"] = cursor + if search_id: + params["searchId"] = search_id + return request_util.get_request(url, params) + + def search_post_v1(self, keyword, cursor: str = None, search_id: str = None): + url = f"{self.base_url}/api/tiktok/search-post/v1" + params = { + "token": self.token, + "keyword": keyword, + } + if cursor: + params["cursor"] = cursor + if search_id: + params["searchId"] = search_id + return request_util.get_request(url, params) + diff --git a/justoneapi/apis/weibo.py b/justoneapi/apis/weibo.py index a4cebe35..87641032 100644 --- a/justoneapi/apis/weibo.py +++ b/justoneapi/apis/weibo.py @@ -63,3 +63,33 @@ def get_user_detail_v1(self, uid: str): } return request_util.get_request(url, params) + def get_fans_v1(self, uid: str, page: int): + url = f"{self.base_url}/api/weibo/get-fans/v1" + params = { + "token": self.token, + "uid": uid, + "page": page, + } + return request_util.get_request(url, params) + + def get_followers_v1(self, uid: str, page: int): + url = f"{self.base_url}/api/weibo/get-followers/v1" + params = { + "token": self.token, + "uid": uid, + "page": page, + } + return request_util.get_request(url, params) + + def get_user_post_v1(self, uid: str, page: int, since_id: str = None): + url = f"{self.base_url}/api/weibo/get-user-post/v1" + params = { + "token": self.token, + "uid": uid, + "page": page, + } + if since_id: + params["sinceId"] = since_id + + return request_util.get_request(url, params) + diff --git a/justoneapi/apis/xiaohongshu.py b/justoneapi/apis/xiaohongshu.py index 310efa39..b282b8c5 100644 --- a/justoneapi/apis/xiaohongshu.py +++ b/justoneapi/apis/xiaohongshu.py @@ -43,6 +43,22 @@ def get_note_detail_v1(self, note_id: str): } return request_util.get_request(url, params) + def get_note_detail_v2(self, note_id: str): + url = f"{self.base_url}/api/xiaohongshu/get-note-detail/v2" + params = { + "token": self.token, + "noteId": note_id, + } + return request_util.get_request(url, params) + + def get_note_detail_v3(self, note_id: str): + url = f"{self.base_url}/api/xiaohongshu/get-note-detail/v3" + params = { + "token": self.token, + "noteId": note_id, + } + return request_util.get_request(url, params) + def get_note_detail_v4(self, note_id: str): url = f"{self.base_url}/api/xiaohongshu/get-note-detail/v4" params = { diff --git a/justoneapi/client.py b/justoneapi/client.py index 38ebfa49..13632d7e 100644 --- a/justoneapi/client.py +++ b/justoneapi/client.py @@ -1,8 +1,11 @@ from justoneapi import config from justoneapi.apis.bilibili import BilibiliAPI from justoneapi.apis.douyin import DouyinAPI +from justoneapi.apis.jd import JdAPI from justoneapi.apis.kuaishou import KuaishouAPI +from justoneapi.apis.search import SearchAPI from justoneapi.apis.taobao import TaobaoAPI +from justoneapi.apis.tiktok import TikTokAPI from justoneapi.apis.user import UserAPI from justoneapi.apis.weibo import WeiboAPI from justoneapi.apis.xiaohongshu import XiaohongshuAPI @@ -26,3 +29,6 @@ def __init__(self, token: str, env: str = "cn"): self.kuaishou = KuaishouAPI(self.token, self.base_url) self.weibo = WeiboAPI(self.token, self.base_url) self.bilibili = BilibiliAPI(self.token, self.base_url) + self.search = SearchAPI(self.token, self.base_url) + self.jd = JdAPI(self.token, self.base_url) + self.tiktok = TikTokAPI(self.token, self.base_url) diff --git a/justoneapi/log.py b/justoneapi/log.py index 311f720f..5aef6305 100644 --- a/justoneapi/log.py +++ b/justoneapi/log.py @@ -2,7 +2,6 @@ logger = logging.getLogger("justoneapi") -# 如果用户未配置过 handler,则添加默认控制台输出 if not logger.hasHandlers(): handler = logging.StreamHandler() formatter = logging.Formatter( diff --git a/pyproject.toml b/pyproject.toml index 7fac0bb6..1f344089 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "justoneapi" -version = "1.9.0" +version = "1.15.2" description = "Official Python SDK for Just One API" authors = [{ name="Just One API", email="support@justoneapi.com" }] readme = "README.md" diff --git a/tests/test_jd.py b/tests/test_jd.py new file mode 100644 index 00000000..597961c1 --- /dev/null +++ b/tests/test_jd.py @@ -0,0 +1,14 @@ +import json +import os +from unittest import TestCase + +from justoneapi.client import JustOneAPIClient + + +class TestJdAPI(TestCase): + client = JustOneAPIClient(token=os.environ.get("JUSTONEAPI_TOKEN")) + + def test_get_item_detail_v1(self): + result, data, message = self.client.jd.get_item_detail_v1(item_id="10151656101707") + if result: + print(json.dumps(data, ensure_ascii=False)) diff --git a/tests/test_search.py b/tests/test_search.py new file mode 100644 index 00000000..1c36c588 --- /dev/null +++ b/tests/test_search.py @@ -0,0 +1,14 @@ +import json +import os +from unittest import TestCase + +from justoneapi.client import JustOneAPIClient + + +class TestSearchAPI(TestCase): + client = JustOneAPIClient(token=os.environ.get("JUSTONEAPI_TOKEN")) + + def test_search_v1(self): + result, data, message, has_next_page = self.client.search.search_v1(keyword="deepseek", source="XIAOHONGSHU", start="2025-07-10 00:00:00", end="2025-08-10 00:00:00") + if result: + print(json.dumps(data, ensure_ascii=False)) diff --git a/tests/test_taobao.py b/tests/test_taobao.py index 4f8fccc2..a96a109e 100644 --- a/tests/test_taobao.py +++ b/tests/test_taobao.py @@ -78,3 +78,7 @@ def test_search_item_list_v1(self): if result: print(json.dumps(data, ensure_ascii=False)) + def test_get_item_comment_v1(self): + result, data, message, has_next_page = self.client.taobao.get_item_comment_v1(item_id="988779079569") + if result: + print(json.dumps(data, ensure_ascii=False))