最近在做一个本地生活服务类的数据分析项目,需要批量获取58同城的商品搜索数据。经过一周的摸索和调试,终于完整走通了58同城item_search接口的对接流程。这个接口对于需要获取分类信息、二手交易、租房等数据的开发者来说非常实用,但官方文档比较简略,实际对接过程中会遇到各种意料之外的问题。
58同城的搜索接口主要提供以下核心能力:
这个接口特别适合以下场景:
首先需要注册58同城开放平台账号(https://open.58.com)。注册完成后,在"我的应用"页面创建新应用,通常需要1-3个工作日审核。建议选择"工具类"应用类型,通过率较高。
重要提示:个人开发者每日调用限额为1000次,企业认证后可提升至1万次。如果数据量较大,建议提前准备多个账号分流。
审核通过后,在应用详情页可以获取三个关键参数:
app_key:应用唯一标识app_secret:接口签名密钥access_token:访问令牌(需定期刷新)建议将这些凭证保存在环境变量中,不要硬编码在代码里。我通常使用dotenv管理这些敏感信息:
python复制# .env文件示例
APP_KEY=your_app_key_here
APP_SECRET=your_secret_here
官方文档地址:https://open.58.com/58api/item_search
需要重点关注以下参数:
keyword:搜索关键词(URL编码)page:分页页码pagesize:每页条数(最大50)sort:排序方式(new/price_asc/price_desc)city:城市编码(如北京为11)使用Python的requests库实现基础调用:
python复制import requests
import hashlib
import time
from urllib.parse import quote
def call_58api(keyword, page=1):
timestamp = str(int(time.time()))
params = {
'appkey': os.getenv('APP_KEY'),
'keyword': quote(keyword),
'page': page,
'pagesize': 50,
'timestamp': timestamp
}
# 生成签名
param_str = '&'.join([f'{k}={v}' for k,v in sorted(params.items())])
sign = hashlib.md5((param_str + os.getenv('APP_SECRET')).encode()).hexdigest()
params['sign'] = sign
response = requests.get(
'https://openapi.58.com/api/item_search',
params=params
)
return response.json()
58同城使用MD5签名机制,具体规则如下:
&连接键值对(key=value格式)例如:
code复制appkey=test&keyword=手机&page=1&pagesize=50×tamp=123456 + app_secret
接口返回的total字段表示总记录数,但实际能获取的最大页数是100页(即使total大于5000)。建议的优化方案:
python复制def batch_query(keyword, total_pages):
results = []
for page in range(1, total_pages+1):
try:
data = call_58api(keyword, page)
if data['items']:
results.extend(data['items'])
time.sleep(1) # 避免频繁调用
except Exception as e:
print(f"Page {page} failed: {str(e)}")
return results
json复制{
"status": 200,
"total": 1245,
"items": [
{
"item_id": "12345678",
"title": "iPhone 13 128G 国行",
"price": 3999,
"location": "朝阳区",
"category": "手机",
"post_time": "2023-05-20"
}
]
}
python复制import re
def clean_price(price_str):
if isinstance(price_str, int):
return price_str
nums = re.findall(r'\d+', str(price_str))
return int(nums[0]) if nums else None
地址信息冗余:
标题关键词污染:
58同城对高频访问有以下限制:
应对方案:
python复制from fake_useragent import UserAgent
headers = {
'User-Agent': UserAgent().random,
'Referer': 'https://www.58.com/'
}
建议的存储结构设计:
| 字段 | 类型 | 描述 |
|---|---|---|
| item_id | VARCHAR(20) | 商品唯一ID |
| title | TEXT | 商品标题 |
| price | INT | 清洗后价格 |
| raw_price | VARCHAR(50) | 原始价格字符串 |
| location | VARCHAR(100) | 详细位置 |
| category | VARCHAR(50) | 分类信息 |
| post_time | DATETIME | 发布时间 |
| crawl_time | DATETIME | 采集时间 |
使用SQLAlchemy实现自动化存储:
python复制from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Item(Base):
__tablename__ = '58_items'
id = Column(Integer, primary_key=True)
item_id = Column(String(20))
title = Column(String(200))
price = Column(Integer)
# 其他字段...
engine = create_engine('sqlite:///data.db')
Base.metadata.create_all(engine)
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| 1001 | 无效签名 | 检查签名算法和参数顺序 |
| 1002 | 参数缺失 | 确认必填参数完整 |
| 1003 | 无权限 | 检查app_key是否有效 |
| 1004 | 频率限制 | 降低请求频率或使用代理 |
| 1005 | 服务异常 | 等待一段时间后重试 |
python复制# 调试用打印语句
print(f"Final URL: {response.request.url}")
python复制import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.json()
async def main(keywords):
async with aiohttp.ClientSession() as session:
tasks = []
for kw in keywords:
url = build_url(kw)
tasks.append(fetch(session, url))
return await asyncio.gather(*tasks)
增量采集:记录最后采集时间,下次只获取新数据
分布式扩展:使用Celery+Redis搭建任务队列
缓存机制:对相同查询条件的结果进行本地缓存
python复制from diskcache import Cache
cache = Cache('api_cache')
@cache.memoize(expire=3600)
def cached_query(keyword):
return call_58api(keyword)
在实际项目中,我建议先从基础版本开始,逐步添加这些优化策略。根据我的经验,最常出现问题的环节是签名生成和分页逻辑,建议重点测试这两个部分。