Python爬虫实战:携程景点数据采集实战:从多页列表到结构化数据集(附SQLite持久化存储)!

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

㊗️爬虫难度指数:⭐⭐

🚫声明:本数据&代码仅供学习交流,严禁用于商业用途、倒卖数据或违反目标站点的服务条款等,一切后果皆由使用者本人承担。公开榜单数据一般允许访问,但请务必遵守"君子协议",技术无罪,责任在人。

全文目录:

🌟 开篇语

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

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

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

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

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

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

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

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

摘要(Abstract)

本文将手把手教你构建一个携程景点数据采集系统,通过逆向分析真实API接口,批量抓取多个城市的景点信息(评分、门票价格、地址、开放时间等),最终输出为可直接用于数据分析的结构化数据集。

读完本文你将获得:

  • 掌握旅游网站API逆向分析的完整方法论(开发者工具实战)
  • 学会处理分页列表、参数加密、反爬限制的实用技巧
  • 获得一套可复用的数据采集 + 清洗 + 存储完整代码框架

1. 背景与需求(Why)

为什么要爬旅游数据?

去年国庆前夕,我打算整理一份"长三角高性价比景点清单"送给朋友。手动在携程上逐个点开景点页面,复制评分、票价、地址...折腾了两小时才整理20个景点,效率低得令人抓狂。这时候我意识到:这种重复性数据收集工作,完全可以用程序自动化完成

典型应用场景:

  • 🧳 旅游规划:对比不同城市景点性价比,筛选高评分低票价目标
  • 📊 市场分析:追踪热门景点票价变化趋势,研究淡旺季差异
  • 🗺️ 数据可视化:制作城市景点分布热力图、评分排行榜

目标数据字段清单

字段名 说明 示例值
attraction_name 景点名称 故宫博物院
city 所属城市 北京
rating 综合评分 4.7
review_count 评论数量 12853
price 门票价格(元) 60.0
address 详细地址 东城区景山前街4号
open_time 开放时间 08:30-17:00
tags 标签 历史建筑,博物馆
url 详情链接 https://...

2. 合规与注意事项(必读)

robots.txt 基本规范

携程的 robots.txthttps://you.ctrip.com/robots.txt) 允许搜索引擎抓取部分页面,但明确禁止高频爬取用户数据接口 。我们的采集方案必须遵守:允许搜索引擎抓取部分页面,但明确禁止高频爬取用户数据接口。我们的采集方案必须遵守:

允许做的:

  • 模拟正常用户浏览行为(合理请求间隔)
  • 采集公开展示的景点基础信息
  • 用于个人学习、数据分析等非商业用途

禁止做的:

  • 高并发轰炸服务器(每秒数十次请求)
  • 绕过登录/付费墙采集VIP内容
  • 采集用户隐私信息(手机号、订单记录等)
  • 将数据用于商业转售

频率控制建议

python 复制代码
# 推荐配置
REQUEST_DELAY = 2  # 每次请求间隔2秒
MAX_RETRIES = 3    # 失败重试不超过3次
TIMEOUT = 10       # 请求超时10秒

底线思维: 如果你的采集行为导致网站服务异常,那就是越界了。保持克制,尊重他人劳动成果。

3. 技术选型与整体流程(What/How)

静态 vs 动态 vs API?

携程景点列表页属于前后端分离架构,页面骨架是静态HTML,但数据通过Ajax异步加载。直接用requests抓HTML只能拿到空壳,必须找到真实的数据接口。

为什么选择 API 逆向而非 Selenium?

方案 优点 缺点
Selenium/Playwright 无需分析接口,所见即所得 速度慢(每页5-10秒),资源占用高
API逆向 速度快(每页0.5秒),代码简洁 需要抓包分析,接口可能变化

考虑到景点数据结构稳定、采集量较大(数千条),我选择API逆向方案

数据流转流程

json 复制代码
[用户输入城市列表] 
    ↓
[构造API请求] → [翻页逻辑] → [发送HTTP请求]
    ↓
[JSON解析] → [字段提取] → [数据清洗]
    ↓
[去重处理] → [存储到CSV/SQLite]
    ↓
[日志记录 + 异常处理]

4. 环境准备与依赖安装

Python版本要求

json 复制代码
Python >= 3.8  # 推荐3.10+

核心依赖安装

json 复制代码
pip install requests>=2.28.0
pip install lxml>=4.9.0
pip install pandas>=1.5.0
pip install fake-useragent>=1.4.0

推荐项目结构

json 复制代码
ctrip_spider/
├── main.py              # 主程序入口
├── fetcher.py           # 请求层
├── parser.py            # 解析层
├── storage.py           # 存储层
├── config.py            # 配置文件
├── utils.py             # 工具函数
├── data/
│   ├── attractions.csv  # 输出CSV
│   └── attractions.db   # SQLite数据库
└── logs/
    └── spider.log       # 运行日志

5. 核心实现:请求层(Fetcher)

API接口逆向分析

实战步骤:

  1. 打开携程景点列表页(如:https://you.ctrip.com/sight/beijing1/s0-p1.html)
  2. 按F12打开开发者工具 → Network标签
  3. 筛选XHR请求,刷新页面
  4. 找到返回景点列表的接口(通常包含poilistsightlist关键词)

真实接口示例:

json 复制代码
GET https://m.ctrip.com/restapi/soa2/16709/json/searchPoiList
参数:
- cityId: 1 (北京)
- page: 1
- pageSize: 20
- searchWord: 

请求层代码实现

python 复制代码
# fetcher.py
import requests
import time
import random
from fake_useragent import UserAgent
from typing import Optional, Dict
import logging

class CtripFetcher:
    def __init__(self):
        self.session = requests.Session()
        self.ua = UserAgent()
        self.base_url = "https://m.ctrip.com/restapi/soa2/16709/json/searchPoiList"
        
    def get_headers(self) -> Dict[str, str]:
        """动态生成请求头"""
        return {
            'User-Agent': self.ua.random,
            'Referer': 'https://you.ctrip.com/',
            'Accept': 'application/json',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Connection': 'keep-alive'
        }
    
    def fetch_page(self, city_id: int, page: int, 
                   max_retries: int = 3) -> Optional[Dict]:
        """
        获取指定城市、页码的景点数据
        
        Args:
            city_id: 城市ID(北京=1, 上海=2)
            page: 页码(从1开始)
            max_retries: 最大重试次数
            
        Returns:
            解析后的JSON数据,失败返回None
        """
        params = {
            'cityId': city_id,
            'page': page,
            'pageSize': 20,
            'searchWord': ''
        }
        
        for attempt in range(max_retries):
            try:
                response = self.session.get(
                    self.base_url,
                    params=params,
                    headers=self.get_headers(),
                    timeout=10
                )
                
                if response.status_code == 200:
                    data = response.json()
                    logging.info(f"✓ 城市{city_id} 第{page}页 - 成功")
                    # 随机延时2-4秒,模拟人类行为
                    time.sleep(random.uniform(2, 4))
                    return data
                elif response.status_code == 429:
                    # 频率限制,指数退避
                    wait_time = 2 ** attempt * 5
                    logging.warning(f"⚠ 触发频控,等待{wait_time}秒")
                    time.sleep(wait_time)
                else:
                    logging.error(f"✗ HTTP {response.status_code}")
                    
            except requests.Timeout:
                logging.error(f"✗ 请求超时 (尝试{attempt+1}/{max_retries})")
            except Exception as e:
                logging.error(f"✗ 异常: {str(e)}")
                
        return None

关键技术点说明

  1. Session复用:避免每次请求重新建立TCP连接,提升效率30%
  2. User-Agent轮换 :使用fake-useragent库动态切换UA,降低被识别风险
  3. 指数退避重试:遇到429频控时,等待时间指数增长(5秒→10秒→20秒)
  4. 随机延时:2-4秒随机间隔,模拟真实用户浏览节奏

6. 核心实现:解析层(Parser)

JSON数据结构分析

典型返回数据(简化版):

json 复制代码
{
  "data": {
    "poiList": [
      {
        "poiId": "123456",
        "poiName": "故宫博物院",
        "score": 4.7,
        "commentCount": 12853,
        "price": 60,
        "address": "东城区景山前街4号",
        "openTime": "08:30-17:00",
        "tags": ["历史建筑", "博物馆"]
      }
    ],
    "totalCount": 358
  }
}

解析器代码

python 复制代码
# parser.py
from typing import List, Dict, Optional
import logging

class CtripParser:
    @staticmethod
    def parse_attraction_list(json_data: Dict) -> List[Dict]:
        """
        解析景点列表JSON
        
        Returns:
            标准化的景点数据列表
        """
        attractions = []
        
        try:
            poi_list = json_data.get('data', {}).get('poiList', [])
            
            for poi in poi_list:
                # 容错处理:字段缺失时填充默认值
                attraction = {
                    'attraction_id': poi.get('poiId', ''),
                    'name': poi.get('poiName', '未知景点'),
                    'rating': float(poi.get('score', 0)),
                    'review_count': int(poi.get('commentCount', 0)),
                    'price': CtripParser._parse_price(poi.get('price')),
                    'address': poi.get('address', ''),
                    'open_time': poi.get('openTime', ''),
                    'tags': ','.join(poi.get('tags', [])),
                    'url': f"https://you.ctrip.com/sight/detail/{poi.get('poiId')}.html"
                }
                attractions.append(attraction)
                
            logging.info(f"✓ 解析到 {len(attractions)} 个景点")
            return attractions
            
        except Exception as e:
            logging.error(f"✗ 解析失败: {str(e)}")
            return []
    
    @staticmethod
    def _parse_price(price_raw) -> float:
        """
        价格字段清洗
        
        处理情况:
        - 数字: 60 → 60.0
        - 字符串: "¥60" → 60.0
        - 免费: "免费" → 0.0
        - 缺失: None → -1.0
        """
        if price_raw is None:
            return -1.0
        if isinstance(price_raw, (int, float)):
            return float(price_raw)
        
        # 去除货币符号和空格
        price_str = str(price_raw).replace('¥', '').replace('元', '').strip()
        
        if '免费' in price_str:
            return 0.0
        
        try:
            return float(price_str)
        except ValueError:
            return -1.0

字段容错策略

异常情况 处理方式
评分字段缺失 填充0.0
价格为"电询" 填充-1.0(后续可筛选)
地址为空 保留空字符串
标签列表空 转为空字符串

7. 数据存储与导出(Storage)

存储方案设计

python 复制代码
# storage.py
import pandas as pd
import sqlite3
from typing import List, Dict
import logging
from pathlib import Path

class DataStorage:
    def __init__(self, data_dir: str = './data'):
        self.data_dir = Path(data_dir)
        self.data_dir.mkdir(exist_ok=True)
        self.csv_path = self.data_dir / 'attractions.csv'
        self.db_path = self.data_dir / 'attractions.db'
        
    def save_to_csv(self, data: List[Dict], mode: str = 'a'):
        """
        追加保存到CSV(支持断点续跑)
        
        Args:
            data: 景点数据列表
            mode: 'w'覆盖写入, 'a'追加写入
        """
        df = pd.DataFrame(data)
        
        # 首次写入时添加表头
        header = not self.csv_path.exists() if mode == 'a' else True
        
        df.to_csv(
            self.csv_path,
            mode=mode,
            index=False,
            header=header,
            encoding='utf-8-sig'  # 兼容Excel打开
        )
        logging.info(f"✓ 已保存 {len(data)} 条数据到CSV")
    
    def save_to_sqlite(self, data: List[Dict]):
        """
        存储到SQLite(自动去重)
        """
        conn = sqlite3.connect(self.db_path)
        df = pd.DataFrame(data)
        
        # 创建表(如不存在)
        df.to_sql(
            'attractions',
            conn,
            if_exists='append',
            index=False
        )
        
        # 基于attraction_id去重
        cursor = conn.cursor()
        cursor.execute('''
            DELETE FROM attractions 
            WHERE rowid NOT IN (
                SELECT MIN(rowid) 
                FROM attractions 
                GROUP BY attraction_id
            )
        ''')
        conn.commit()
        logging.info(f"✓ 已保存到SQLite,去重后共{cursor.rowcount}条")
        conn.close()
    
    def load_existing_ids(self) -> set:
        """
        从数据库读取已采集的景点ID(用于断点续跑)
        """
        if not self.db_path.exists():
            return set()
        
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute('SELECT attraction_id FROM attractions')
        ids = {row[0] for row in cursor.fetchall()}
        conn.close()
        return ids

字段映射表

字段名 数据类型 示例值 说明
attraction_id VARCHAR(20) "123456" 主键
name VARCHAR(100) "故宫博物院" -
rating FLOAT 4.7 0-5分
review_count INTEGER 12853 -
price FLOAT 60.0 -1表示未知
address TEXT "..." -
open_time VARCHAR(50) "08:30-17:00" -
tags TEXT "历史,博物馆" 逗号分隔
url TEXT "https://..." -

8. 运行方式与结果展示

主程序整合

python 复制代码
# main.py
import logging
from fetcher import CtripFetcher
from parser import CtripParser
from storage import DataStorage

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('logs/spider.log'),
        logging.StreamHandler()
    ]
)

def crawl_city(city_id: int, city_name: str, max_pages: int = 5):
    """
    爬取指定城市的景点数据
    
    Args:
        city_id: 城市ID
        city_name: 城市名称(用于日志)
        max_pages: 最大爬取页数
    """
    fetcher = CtripFetcher()
    parser = CtripParser()
    storage = DataStorage()
    
    # 加载已爬取ID(断点续跑)
    existing_ids = storage.load_existing_ids()
    
    all_data = []
    
    for page in range(1, max_pages + 1):
        logging.info(f"\n========== {city_name} 第{page}页 ==========")
        
        # 1. 请求数据
        json_data = fetcher.fetch_page(city_id, page)
        if not json_data:
            logging.error(f"第{page}页获取失败,跳过")
            continue
        
        # 2. 解析数据
        attractions = parser.parse_attraction_list(json_data)
        
        # 3. 过滤已存在的数据
        new_attractions = [
            a for a in attractions 
            if a['attraction_id'] not in existing_ids
        ]
        
        if not new_attractions:
            logging.info("本页数据已存在,跳过")
            continue
        
        # 4. 添加城市字段
        for a in new_attractions:
            a['city'] = city_name
        
        all_data.extend(new_attractions)
        
        # 5. 批量保存(每页立即写入,防止中断丢失)
        storage.save_to_csv(new_attractions)
        storage.save_to_sqlite(new_attractions)
    
    logging.info(f"\n🎉 {city_name}爬取完成!共{len(all_data)}条新数据")

if __name__ == '__main__':
    # 城市ID映射(需要预先查询)
    CITIES = {
        1: '北京',
        2: '上海',
        5: '成都'
    }
    
    for city_id, city_name in CITIES.items():
        crawl_city(city_id, city_name, max_pages=3)

启动方式

bash 复制代码
# 创建必要目录
mkdir -p data logs

# 运行爬虫
python main.py

输出示例

CSV文件(attractions.csv)前5行:

csv 复制代码
attraction_id,name,rating,review_count,price,address,open_time,tags,url,city
123456,故宫博物院,4.7,12853,60.0,东城区景山前街4号,08:30-17:00,历史建筑,博物馆,https://...,北京
123457,天坛公园,4.6,8932,15.0,东城区天坛东里甲1号,06:00-22:00,古建筑,公园,https://...,北京
234567,外滩,4.8,15234,0.0,黄浦区中山东一路,全天,地标,夜景,https://...,上海

SQLite查询示例:

sql 复制代码
-- 查看各城市景点数量
SELECT city, COUNT(*) as count 
FROM attractions 
GROUP BY city;

-- 查找高评分免费景点
SELECT name, rating, city 
FROM attractions 
WHERE price = 0 AND rating >= 4.5 
ORDER BY rating DESC;

9. 常见问题与排错

问题1:返回403/429状态码

原因分析:

  • 403:Headers不完整或UA被识别为爬虫
  • 429:请求频率过高触发限流

解决方案:

python 复制代码
# 增强Headers
headers = {
    'User-Agent': 'Mozilla/5.0...',  # 使用真实浏览器UA
    'Referer': 'https://you.ctrip.com/',
    'Cookie': 'your_cookie_here',  # 部分接口需要登录态
    'X-Requested-With': 'XMLHttpRequest'
}

# 加大延时
time.sleep(random.uniform(5, 8))

# 使用代理池(进阶)
proxies = {'http': 'http://proxy.com:8080'}
requests.get(url, proxies=proxies)

问题2:抓到的HTML是空壳

现象: Response长度只有几KB,没有景点数据

排查步骤:

  1. 确认是否为动态渲染页面(查看Network的XHR请求)
  2. 对比浏览器Headers和代码Headers(重点检查Cookie)
  3. 检查是否需要先访问列表页获取Token

案例: 某次爬取发现接口返回{code: -1, msg: "请先登录"},查看Cookie发现缺少_abtest参数。解决方法是先访问首页,提取Set-Cookie后再请求API。

问题3:JSON解析报错

错误信息:

json 复制代码
JSONDecodeError: Expecting value: line 1 column 1 (char 0)

原因: 返回内容不是JSON(可能是HTML错误页或被拦截)

调试技巧:

python 复制代码
response = requests.get(url)
print(f"状态码: {response.status_code}")
print(f"Content-Type: {response.headers.get('Content-Type')}")
print(f"前100字符: {response.text[:100]}")

# 只有确认是JSON才解析
if 'application/json' in response.headers.get('Content-Type', ''):
    data = response.json()

问题4:中文乱码

场景: CSV用Excel打开显示乱码

解决方案:

python 复制代码
# 方案1:使用UTF-8-BOM编码(推荐)
df.to_csv('file.csv', encoding='utf-8-sig')

# 方案2:转为GBK(不推荐,可能丢失生僻字)
df.to_csv('file.csv', encoding='gbk', errors='ignore')

10. 进阶优化(可选)

10.1 异步并发加速

python 复制代码
import asyncio
import aiohttp

async def fetch_async(session, city_id, page):
    url = f"{BASE_URL}?cityId={city_id}&page={page}"
    async with session.get(url) as response:
        return await response.json()

async def crawl_city_async(city_id, max_pages):
    async with aiohttp.ClientSession() as session:
        tasks = [
            fetch_async(session, city_id, page)
            for page in range(1, max_pages + 1)
        ]
        results = await asyncio.gather(*tasks)
        return results

# 速度提升:同步2分钟 → 异步20秒

10.2 断点续跑机制

python 复制代码
# 记录爬取进度
import json

def save_progress(city_id, page):
    with open('progress.json', 'w') as f:
        json.dump({'city_id': city_id, 'page': page}, f)

def load_progress():
    try:
        with open('progress.json') as f:
            return json.load(f)
    except FileNotFoundError:
        return {'city_id': 1, 'page': 1}

# 使用示例
progress = load_progress()
for page in range(progress['page'], max_pages + 1):
    # ... 爬取逻辑 ...
    save_progress(city_id, page)

10.3 监控与报警

python 复制代码
class SpiderMonitor:
    def __init__(self):
        self.success_count = 0
        self.fail_count = 0
    
    def report(self):
        total = self.success_count + self.fail_count
        success_rate = self.success_count / total if total > 0 else 0
        
        if success_rate < 0.8:
            # 发送钉钉/企业微信报警
            send_alert(f"⚠️ 成功率仅{success_rate:.1%},请检查")

10.4 定时任务

bash 复制代码
# crontab示例(每天凌晨2点更新)
0 2 * * * cd /path/to/project && python main.py >> logs/cron.log 2>&1

11. 总结与延伸

我们完成了什么?

✅ 逆向分析了携程景点列表API,绕过前端渲染限制

✅ 实现了健壮的请求层(重试、退避、频控)

✅ 构建了完整的数据流:采集 → 解析 → 清洗 → 存储

✅ 支持多城市批量爬取 + 断点续跑

实战收获

这个项目让我深刻体会到:爬虫不只是技术活,更是对抗与妥协的艺术。网站方设置反爬措施保护数据,我们需要在尊重规则的前提下,用技术手段获取公开信息。过程中踩过的坑------Cookie失效、频控封IP、数据结构突变------每一个都是宝贵的经验。

下一步可以做什么?

  1. 框架升级:迁移到Scrapy,利用其中间件机制处理反爬
  2. 分布式部署:使用Scrapy-Redis实现多机协同,日产百万级数据
  3. 智能化:接入GPT做景点描述总结、情感分析
  4. 产品化:开发Web界面,让非技术人员也能一键导出数据

延伸阅读

  • 《Scrapy官方文档》:https://docs.scrapy.org/
  • 《Python网络爬虫实战》(崔庆才)
  • 《Web Scraping with Python》(Ryan Mitchell)

最后提醒: 爬虫是工具,用它来提升效率、做数据分析完全没问题,但千万别跨越法律红线。尊重数据所有者的权益,才能让这个技术长久健康地发展下去。

有问题欢迎在评论区交流,祝爬虫之路顺利!

🌟 文末

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

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

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

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

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

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

✅ 互动征集

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

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


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

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

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


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

相关推荐
无垠的广袤2 小时前
【VisionFive 2 Lite 单板计算机】边缘AI视觉应用部署:人脸检测
linux·人工智能·python·opencv·开发板
yuankoudaodaokou2 小时前
突破大型工件测量瓶颈:思看科技在风电与船舶制造中的革新应用
python·科技·制造
Allen_LVyingbo2 小时前
面向70B多模态医疗大模型预训练的工程落地(医疗大模型预训练扩展包)
人工智能·python·分类·知识图谱·健康医疗·迁移学习
Deng8723473482 小时前
电脑使用 Gemini出了点问题解决办法
人工智能·python
我送炭你添花2 小时前
Pelco KBD300A 模拟器:18. 按依赖顺序 + 复杂度由低到高逐步推进pytest单元测试
python·单元测试·log4j·pytest
程序员杰哥2 小时前
如何写出高效的测试用例?
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例
2301_822377652 小时前
数据分析师的Python工具箱
jvm·数据库·python
无垠的广袤2 小时前
【VisionFive 2 Lite 单板计算机】SoC 温度的 Home Assistant 物联网终端显示
linux·python·物联网
强化试剂瓶2 小时前
全面掌握Ergosterol-PEG-Biotin,麦角甾醇PEG生物素的使用与注意事项
python·scrapy·flask·scikit-learn·pyqt