Python爬虫零基础入门【第九章:实战项目教学·第2节】“接口优先“项目:从 Network 还原 JSON 接口分页!

🔥本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:

      • [🌟 开篇语](#🌟 开篇语)
      • [📚 上期回顾](#📚 上期回顾)
      • [🎯 本篇目标](#🎯 本篇目标)
      • [💡 为什么"接口优先"?](#💡 为什么"接口优先"?)
      • [🔍 实战演示:从 Network 还原接口](#🔍 实战演示:从 Network 还原接口)
        • 案例站点:某科技资讯网
          • [步骤 1:打开 Network 面板](#步骤 1:打开 Network 面板)
          • [步骤 2:识别数据接口](#步骤 2:识别数据接口)
          • [步骤 3:分析请求参数](#步骤 3:分析请求参数)
          • [步骤 4:用 Python 还原请求](#步骤 4:用 Python 还原请求)
      • [🛠️ 完整 Demo 代码](#🛠️ 完整 Demo 代码)
        • 项目结构
        • [模块 1:配置文件](#模块 1:配置文件)
        • [模块 2:API 客户端](#模块 2:API 客户端)
        • [模块 3:数据转换器](#模块 3:数据转换器)
        • [模块 4:API 爬虫引擎](#模块 4:API 爬虫引擎)
        • [模块 5:数据库管理](#模块 5:数据库管理)
        • [模块 6:主程序](#模块 6:主程序)
      • [📊 运行效果](#📊 运行效果)
      • [🚀 快速适配新站点](#🚀 快速适配新站点)
      • [📝 小结](#📝 小结)
      • [🎯 下期预告](#🎯 下期预告)
      • [🌟 文末](#🌟 文末)
        • [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
        • [✅ 互动征集](#✅ 互动征集)

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略反爬对抗 ,从数据清洗分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上

📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》

订阅后更新会优先推送,按目录学习更高效~

📚 上期回顾

上一篇《通用新闻采集器:从零打造可复用的静态站模板》我们打造了通用静态新闻采集器,用配置化方式快速适配多个站点。但你有没有发现一个问题:很多现代网站根本不是静态的------打开浏览器能看到数据,查看源代码却是空的。

这时候很多人会直接上 Playwright,启动浏览器、等待渲染、提取数据......但这真的是最优方案吗?

99% 的情况下,答案是 NO

今天我们就来学习降维打击的终极技巧 :用浏览器开发者工具找到真正的数据接口,然后用轻量级的 requests 直接抓取 JSON------速度快 10 倍,稳定性更高!🚀

🎯 本篇目标

看完这篇,你能做到:

  1. 熟练使用 Network 面板找接口(核心技能)
  2. 分析接口参数规律(分页、筛选、加密)
  3. 还原接口请求逻辑(Headers、参数构造)
  4. 封装通用 API 采集器(可复用模板)

验收标准:从 3 个动态网站找到数据接口,用 requests 采集各 200+ 条数据

💡 为什么"接口优先"?

性能对比实测
方案 采集 200 条耗时 内存占用 稳定性
Playwright ~60秒 300-500MB ⭐⭐⭐
API 直采 ~5秒 10-20MB ⭐⭐⭐⭐⭐

效率提升 12 倍!

真实案例

某电商商品搜索页:

浏览器看到的:

html 复制代码
<div id="app">
  <!-- 由 JavaScript 渲染 -->
</div>
<script src="app.js"></script>

背后的真相:

javascript 复制代码
// app.js 中的请求
fetch('https://api.example.com/search', {
  method: 'POST',
  body: JSON.stringify({
    keyword: '手机',
    page: 1,
    pageSize: 20
  })
})

直接抓接口:

python 复制代码
import requests

resp = requests.post('https://api.example.com/search', json={
    'keyword': '手机',
    'page': 1,
    'pageSize': 20
})

data = resp.json()  # 直接拿到 JSON 数据!

🔍 实战演示:从 Network 还原接口

案例站点:某科技资讯网

我们以一个真实场景演示完整流程(以 IT 之家为例)。

步骤 1:打开 Network 面板
  1. 打开目标网站(如 https://www.ithome.com)
  2. F12 打开开发者工具
  3. 切换到 Network 标签
  4. 勾选 Preserve log(保留日志)
  5. 点击筛选器选择 Fetch/XHR(只看 Ajax 请求)
  6. 刷新页面 F5
步骤 2:识别数据接口

观察请求列表,找名字像 API 的:

json 复制代码
✅ 这是数据接口:
newslist
api/news/getlist
search.json
data/list

❌ 这不是数据接口:
app.js
logo.png
analytics.js

点击可疑请求,查看 PreviewResponse

json 复制代码
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "newsid": "123456",
        "title": "苹果发布新品",
        "postdate": "2026-01-23 10:00:00"
      }
    ],
    "total": 500,
    "pageSize": 20
  }
}

看到这种结构,就是数据接口!

步骤 3:分析请求参数

切换到 Headers 标签,记录:

json 复制代码
Request URL: https://api.ithome.com/api/news/newslistpageget
Request Method: POST

Request Headers:
  User-Agent: Mozilla/5.0...
  Content-Type: application/json
  Referer: https://www.ithome.com
  
Request Payload:
{
  "categoryid": "0",
  "type": "0",
  "page": 1,
  "pageSize": 20
}
步骤 4:用 Python 还原请求
python 复制代码
import requests

url = 'https://api.ithome.com/api/news/newslistpageget'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Content-Type': 'application/json',
    'Referer': 'https://www.ithome.com'
}

payload = {
    'categoryid': '0',
    'type': '0',
    'page': 1,
    'pageSize': 20
}

resp = requests.post(url, json=payload, headers=headers, timeout=10)
print(resp.status_code)
print(resp.json())

运行成功,拿到数据! 🎉

🛠️ 完整 Demo 代码

项目结构
json 复制代码
api_spider_demo/
├── config/
│   ├── sites/
│   │   ├── ithome.yaml      # IT之家配置
│   │   ├── zhihu.yaml       # 知乎配置
│   │   └── template.yaml    # 配置模板
│   └── spider.yaml
│
├── src/
│   ├── core/
│   │   ├── api_client.py    # API 客户端
│   │   ├── database.py      # 数据库管理
│   │   └── spider.py        # 爬虫引擎
│   │
│   └── adapters/
│       ├── base.py          # 适配器基类
│       └── ithome.py        # IT之家适配器
│
├── data/
│   └── news.db
├── logs/
├── requirements.txt
├── main.py
└── README.md
模块 1:配置文件
yaml 复制代码
# config/sites/ithome.yaml
site:
  name: "IT之家"
  domain: "https://www.ithome.com"

api:
  list:
    url: "https://api.ithome.com/api/news/newslistpageget"
    method: "POST"  # GET / POST
    
    headers:
      User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
      Content-Type: "application/json"
      Referer: "https://www.ithome.com"
    
    params:  # GET 参数
      {}
    
    payload:  # POST 参数
      categoryid: "0"
      type: "0"
      page: "{page}"  # 使用占位符
      pageSize: 20
    
    response:
      data_path: "data.list"  # 数据在响应中的路径
      total_path: "data.total"
      
  detail:
    url: "https://api.ithome.com/api/news/detail/{newsid}"
    method: "GET"
    
    params:
      newsid: "{newsid}"

fields:
  mapping:
    # API 字段 → 数据库字段
    newsid: "article_id"
    title: "title"
    postdate: "publish_time"
    summary: "summary"
    url: "detail_url"
    
  transforms:
    # 字段转换规则
    publish_time:
      type: "datetime"
      format: "%Y-%m-%d %H:%M:%S"

pagination:
  type: "page_number"  # page_number / offset / cursor
  start_page: 1
  max_pages: 10
  page_size: 20

incremental:
  enabled: true
  field: "publish_time"
  direction: "desc"  # desc / asc
模块 2:API 客户端
python 复制代码
# src/core/api_client.py
import requests
import json
from typing import Dict, Any, Optional

class APIClient:
    """通用 API 客户端"""
    
    def __init__(self, config: Dict):
        self.config = config
        self.session = requests.Session()
        self._setup_session()
    
    def _setup_session(self):
        """配置会话"""
        # 设置默认 headers
        headers = self.config['api']['list'].get('headers', {})
        self.session.headers.update(headers)
    
    def fetch_list(self, page: int) -> Optional[Dict]:
        """
        获取列表数据
        
        Args:
            page: 页码
        
        Returns:
            dict: {items: [...], total: 100}
        """
        api_config = self.config['api']['list']
        url = api_config['url']
        method = api_config['method'].upper()
        
        # 构造参数
        params = self._build_params(api_config.get('params', {}), page)
        payload = self._build_params(api_config.get('payload', {}), page)
        
        print(f"📡 请求接口:{method} {url}")
        print(f"   页码:{page}")
        
        try:
            if method == 'GET':
                resp = self.session.get(url, params=params, timeout=15)
            else:  # POST
                resp = self.session.post(url, json=payload, params=params, timeout=15)
            
            resp.raise_for_status()
            data = resp.json()
            
            # 提取数据
            items = self._extract_data(data, api_config['response']['data_path'])
            total = self._extract_data(data, api_config['response'].get('total_path'))
            
            print(f"   ✅ 获得 {len(items)} 条数据")
            
            return {
                'items': items,
                'total': total
            }
            
        except Exception as e:
            print(f"   ❌ 请求失败:{e}")
            return None
    
    def fetch_detail(self, article_id: str) -> Optional[Dict]:
        """获取详情数据"""
        if 'detail' not in self.config['api']:
            return None
        
        api_config = self.config['api']['detail']
        url = api_config['url'].format(newsid=article_id)
        method = api_config['method'].upper()
        
        try:
            if method == 'GET':
                resp = self.session.get(url, timeout=15)
            else:
                resp = self.session.post(url, timeout=15)
            
            resp.raise_for_status()
            return resp.json()
            
        except Exception as e:
            print(f"   ❌ 详情请求失败:{e}")
            return None
    
    def _build_params(self, template: Dict, page: int) -> Dict:
        """构造参数(替换占位符)"""
        params = {}
        
        for key, value in template.items():
            if isinstance(value, str) and '{page}' in value:
                params[key] = page
            elif isinstance(value, str) and value.startswith('{') and value.endswith('}'):
                # 保留占位符,后续处理
                params[key] = value
            else:
                params[key] = value
        
        return params
    
    def _extract_data(self, response: Dict, path: str) -> Any:
        """
        从响应中提取数据
        
        Args:
            response: 完整响应
            path: 数据路径(如 'data.list')
        """
        if not path:
            return response
        
        keys = path.split('.')
        data = response
        
        for key in keys:
            if isinstance(data, dict):
                data = data.get(key)
            else:
                return None
        
        return data
模块 3:数据转换器
python 复制代码
# src/core/transformer.py
from datetime import datetime
from hashlib import md5
from typing import Dict, Any

class DataTransformer:
    """数据转换器"""
    
    def __init__(self, config: Dict):
        self.config = config
        self.field_mapping = config['fields']['mapping']
        self.transforms = config['fields'].get('transforms', {})
    
    def transform_list_item(self, raw_item: Dict) -> Dict:
        """
        转换列表项
        
        Args:
            raw_item: API 返回的原始数据
        
        Returns:
            dict: 标准化后的数据
        """
        item = {}
        
        # 字段映射
        for api_field, db_field in self.field_mapping.items():
            value = raw_item.get(api_field)
            
            if value is not None:
                # 应用转换规则
                if db_field in self.transforms:
                    value = self._apply_transform(value, self.transforms[db_field])
                
                item[db_field] = value
        
        # 生成去重键
        source = self.config['site']['name']
        unique_id = raw_item.get('newsid') or raw_item.get('id') or raw_item.get('url')
        item['dedup_key'] = md5(f"{source}_{unique_id}".encode()).hexdigest()
        
        # 添加来源
        item['source'] = source
        
        return item
    
    def _apply_transform(self, value: Any, rules: Dict) -> Any:
        """应用转换规则"""
        transform_type = rules.get('type')
        
        if transform_type == 'datetime':
            # 时间转换
            date_format = rules.get('format', '%Y-%m-%d %H:%M:%S')
            try:
                dt = datetime.strptime(value, date_format)
                return {
                    'datetime': dt.strftime('%Y-%m-%d %H:%M:%S'),
                    'timestamp': int(dt.timestamp())
                }
            except:
                return value
        
        elif transform_type == 'int':
            try:
                return int(value)
            except:
                return value
        
        return value
模块 4:API 爬虫引擎
python 复制代码
# src/core/spider.py
import yaml
from pathlib import Path
from .api_client import APIClient
from .transformer import DataTransformer
from .database import DatabaseManager
import time

class APISpider:
    """API 爬虫引擎"""
    
    def __init__(self, config_file: str):
        self.config = self._load_config(config_file)
        self.client = APIClient(self.config)
        self.transformer = DataTransformer(self.config)
        self.db = DatabaseManager()
        
        self.site_name = self.config['site']['name']
    
    def _load_config(self, config_file: str) -> Dict:
        """加载配置"""
        with open(config_file, 'r', encoding='utf-8') as f:
            return yaml.safe_load(f)
    
    def run(self):
        """运行采集"""
        print("="*60)
        print(f"🚀 开始采集:{self.site_name}")
        print("="*60)
        
        pagination = self.config['pagination']
        start_page = pagination['start_page']
        max_pages = pagination['max_pages']
        
        all_items = []
        
        # 增量边界
        last_boundary = None
        if self.config['incremental']['enabled']:
            last_boundary = self.db.get_last_boundary(
                self.site_name,
                self.config['incremental']['field']
            )
            if last_boundary:
                print(f"📌 增量边界:{last_boundary}")
        
        # 循环采集
        for page in range(start_page, start_page + max_pages):
            result = self.client.fetch_list(page)
            
            if not result or not result['items']:
                print(f"🛑 第 {page} 页无数据")
                break
            
            # 转换数据
            items = []
            for raw_item in result['items']:
                item = self.transformer.transform_list_item(raw_item)
                
                # 增item, last_boundary):
                    print(f"⏭️  已到增量边界")
                    self._save_items(all_items + items)
                    return
                
                items.append(item)
            
            all_items.extend(items)
            print(f"   累计:{len(all_items)} 条")
            
            time.sleep(1)  # 礼貌延迟
        
        # 保存数据
        self._save_items(all_items)
        
        # 统计
        self._print_stats()
    
    def _should_stop(self, item: Dict, boundary: str) -> bool:
        """判断是否到达增量边界"""
        field = self.config['incremental']['field']
        direction = self.config['incremental'].get('direction', 'desc')
        
        current_value = item.get(field)
        
        if not current_value:
            return False
        
        # 时间字段特殊处理
        if isinstance(current_value, dict) and 'datetime' in current_value:
            current_value = current_value['datetime']
        
        if direction == 'desc':
            return str(current_value) <= str(boundary)
        else:
            return str(current_value) >= str(boundary)
    
    def _save_items(self, items: list):
        """保存数据"""
        if not items:
            return
        
        inserted, skipped = self.db.batch_save(items)
        print(f"\n💾 数据保存:新增 {inserted},跳过 {skipped}")
    
    def _print_stats(self):
        """打印统计"""
        stats = self.db.get_stats(self.site_name)
        
        print("\n" + "="*60)
        print("📊 采集统计")
        print("="*60)
        print(f"来源:{self.site_name}")
        print(f"总计:{stats.get('total', 0)} 条")
        print(f"成功:{stats.get('success', 0)} 条")
        print("="*60)
模块 5:数据库管理
python 复制代码
# src/core/database.py
import sqlite3
from typing import List, Dict, Tuple

class DatabaseManager:
    """数据库管理器"""
    
    def __init__(self, db_path: str = "data/news.db"):
        self.conn = sqlite3.connect(db_path, check_same_thread=False)
        self._init_db()
    
    def _init_db(self):
        """初始化数据库"""
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS articles (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                dedup_key TEXT UNIQUE NOT NULL,
                source TEXT NOT NULL,
                article_id TEXT,
                title TEXT NOT NULL,
                detail_url TEXT,
                publish_time TEXT,
                publish_timestamp INTEGER,
                summary TEXT,
                content TEXT,
                crawl_status TEXT DEFAULT 'SUCCESS',
                created_at TEXT DEFAULT (datetime('now'))
            )
        """)
        
        self.conn.execute("CREATE INDEX IF NOT EXISTS idx_source ON articles(source)")
        self.conn.execute("CREATE INDEX IF NOT EXISTS idx_pub_time ON articles(publish_timestamp)")
        
        self.conn.commit()
    
    def batch_save(self, items: List[Dict]) -> Tuple[int, int]:
        """批量保存"""
        inserted = 0
        skipped = 0
        
        for item in items:
            try:
                # 处理时间字段
                publish_time = item.get('publish_time')
                if isinstance(publish_time, dict):
                    item['publish_time'] = publish_time.get('datetime')
                    item['publish_timestamp'] = publish_time.get('timestamp')
                
                self.conn.execute("""
                    INSERT INTO articles (
                        dedup_key, source, article_id, title, 
                        detail_url, publish_time, publish_timestamp, summary
                    )
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                """, (
                    item['dedup_key'],
                    item['source'],
                    item.get('article_id'),
                    item['title'],
                    item.get('detail_url'),
                    item.get('publish_time'),
                summary')
                ))
                inserted += 1
            except sqlite3.IntegrityError:
                skipped += 1
        
        self.conn.commit()
        return inserted, skipped
    
    def get_last_boundary(self, source: str, field: str) -> str:
        """获取增量边界"""
        cursor = self.conn.execute(f"""
            SELECT {field}
            FROM articles
            WHERE source = ?
            ORDER BY created_at DESC
            LIMIT 1
        """, (source,))
        
        row = cursor.fetchone()
        return row[0] if row else None
    
    def get_stats(self, source: str = None) -> Dict:
        """获取统计"""
        if source:
            cursor = self.conn.execute("""
                SELECT 
                    COUNT(*) as total,
                    SUM(CASE WHEN crawl_status = 'SUCCESS' THEN 1 ELSE 0 END) as success
                FROM articles
                WHERE source = ?
            """, (source,))
        else:
            cursor = self.conn.execute("""
                SELECT 
                    COUNT(*) as total,
                    SUM(CASE WHEN crawl_status = 'SUCCESS' THEN 1 ELSE 0 END) as success
                FROM articles
            """)
        
        row = cursor.fetchone()
        return {
            'total': row[0],
            'success': row[1]
        }
模块 6:主程序
python 复制代码
# main.py
from src.core.spider import APISpider
from pathlib import Path

def main():
    """主程序"""
    # 选择站点配置
    config_file = 'config/sites/ithome.yaml'
    
    if not Path(config_file).exists():
        print(f"❌ 配置文件不存在:{config_file}")
        return
    
    # 创建爬虫
    spider = APISpider(config_file)
    
    # 运行采集
    spider.run()

if __name__ == '__main__':
    main()

📊 运行效果

json 复制代码
$ python main.py

==================================================
🚀 开始采集:IT之家
==================================================
📌 增量边界:2026-01-22 15:30:00

📡 请求接口:POST https://api.ithome.com/api/news/newslistpageget
   页码:1
   ✅ 获得 20 条数据
   累计:20 条

📡 请求接口:POST https://api.ithome.com/api/news/newslistpageget
   页码:2
   ✅ 获得 20 条数据
   累计:40 条

📡 请求接口:POST https://api.ithome.com/api/news/newslistpageget
   页码:3
   ✅ 获得 15 条数据
⏭️  已到增量边界

💾 数据保存:新增 15,跳过 40

==================================================
📊 采集统计
==================================================
来源:IT之家
总计:255 条
成功:255 条
==================================================

🚀 快速适配新站点

三步适配

步骤 1:找接口并创建配置

yaml 复制代码
# config/sites/zhihu.yaml
site:
  name: "知乎"
  domain: "https://www.zhihu.com"

api:
  list:
    url: "https://www.zhihu.com/api/v4/questions"
    method: "GET"
    
    params:
      offset: "{offset}"
      limit: 20
    
    response:
      data_path: "data"
      total_path: "paging.totals"

fields:
  mapping:
    id: "article_id"
    title: "title"
    created: "publish_time"
# ...

步骤 2:运行采集

python 复制代码
spider = APISpider('config/sites/zhihu.yaml')
spider.run()

步骤 3:验证数据

bash 复制代码
sqlite3 data/news.db "SELECT COUNT(*) FROM articles WHERE source='知乎'"

📝 小结

今天我们打造了基于 API 的通用采集器

  1. 接口发现技巧(Network 面板使用)
  2. 参数还原方法(Headers、Payload 分析)
  3. 通用框架设计(配置化、可复用)
  4. 完整 Demo 代码(拿来即用)

记住核心原则:能用 API 就不用浏览器,能用 requests 就不用 Playwright。先花 5 分钟找接口,能节省后续 90% 的时间!

🎯 下期预告

接口找到了,但有些接口参数加密怎么办?sign、token 从哪来?

下一篇《31|接口参数逆向入门:定位加密函数、还原签名逻辑》,我们会学习如何破解接口加密,让你能应对更复杂的反爬场景!

验收作业:从 3 个动态网站找到数据接口,用这个模板采集各 200+ 条数据。把配置文件和数据截图发我!加油!

🌟 文末

好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

📌 专栏持续更新中|建议收藏 + 订阅

专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:

✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集

想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?

评论区留言告诉我你的需求,我会优先安排更新 ✅


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。

相关推荐
将心ONE2 小时前
QwenTTS 预设音色
python
冷雨夜中漫步2 小时前
Python入门——字符串
开发语言·python
Yvonne爱编码2 小时前
Java 接口学习核心难点深度解析
java·开发语言·python
June bug2 小时前
(#数组/链表操作)合并两个有重复元素的无序数组,返回无重复的有序结果
数据结构·python·算法·leetcode·面试·跳槽
人工智能AI技术2 小时前
【Agent从入门到实践】33 集成多工具,实现Agent的工具选择与执行
人工智能·python
AIFQuant3 小时前
如何通过股票数据 API 计算 RSI、MACD 与移动平均线MA
大数据·后端·python·金融·restful
70asunflower3 小时前
Python with 语句与上下文管理完全教程
linux·服务器·python
deephub3 小时前
为什么标准化要用均值0和方差1?
人工智能·python·机器学习·标准化
hnxaoli3 小时前
win10程序(十五)归档文件的xlsx目录自动分卷
python