Python/爬虫学习记录-Day05

1. 爬取时遇到严格的 Cookie 限制怎么办?

1.1 模拟真人登录,将 Cookie 存起来用

  • 就像真人每次登录后浏览器会记住登录状态一样,我们可以用自动化工具(比如 Selenium 或 Playwright)模拟整个登录过程:打开登录页面、输入账号密码、点登录按钮。

  • 登录成功后,把浏览器里生成的 Cookie 完整地抓取下来。

1.2 建立 Cookie 池

  • 不能只用一个账号登录一次,因为 Cookie 会过期,单个账号频繁用也容易被封。所以准备一批账号(比如几十上百个)。

  • 把这些账号登录后拿到的 Cookie,存到一个地方(比如 Redis 或者数据库),每个 Cookie 都标记好是哪个账号的、什么时候获取的、用了多少次。

1.3 随机抽取 Cookie

  • 每次爬虫要访问需要登录的页面时,就从 Cookie 池中随机抽一个有效的 Cookie 来用。

  • 如何知道有效?定期检查:比如用这个 Cookie 去访问个人主页,看能不能成功,如果失败了或者提示要登录,就说明这个 Cookie 无效。

1.4 定时刷新 Cookie

  • 不能等 Cookie 失效了才去管。我会设置一个任务,比如每隔几小时,自动用那些账号重新登录一遍,把新的 Cookie 替换掉旧的。有点像定期给会员卡续期。

1.5 模拟真人行为

  • 光有 Cookie 可能还不够,网站还会看你怎么用它:

    • 每次请求用不同的浏览器标识(User-Agent)。

    • 请求之间的间隔时间不要太固定,模仿真人浏览的停顿(比如随机等个1-5秒)。

    • 如果网站要求高,可以用工具模拟鼠标移动、滚动页面这些真人动作。

辅助理解

# Cookie池管理核心逻辑
class CookieManager:
    def __init__(self):
        self.redis = Redis(connection_pool=pool)
    
    def get_valid_cookie(self):
        """动态选择最优Cookie"""
        active_cookies = self.redis.zrangebyscore('cookies', min=time()-3600, max='+inf')
        return random.choice(active_cookies) if active_cookies else self.refresh_cookie()
    
    def refresh_cookie(self, account):
        """模拟登录更新Cookie"""
        driver = uc.Chrome()
        driver.get(login_url)
        # ...执行登录流程...
        cookies = {c['name']:c['value'] for c in driver.get_cookies()}
        self.redis.zadd('cookies', {json.dumps(cookies): time()})
        return cookies

2. 如果作为小某书开发人员,如何防止别人爬数据?

2.1 识别机器流量

  • 检查基本特征:看看请求是不是来自常见的爬虫工具(比如Scrapy的默认Header)、请求速度是不是快得不正常(真人不可能1秒点10次)。

  • 验证码拦截:对于可疑的请求(比如同一个IP短时间请求太多),弹出验证码(图形、滑块、点选)。

2.2 让爬虫“看不懂”网页

  • 混淆关键数据:页面上的用户ID、内容ID这些重要信息,在HTML源码里不要直接用12345这种数字,可以变成x123y456z这种编码,或者每次加载变一变。

  • 数据动态加载:重要的内容(笔记、评论)不直接写在最初的HTML里。等页面加载完,让浏览器再通过AJAX请求去拿。这样爬虫只拿HTML就没用了。

  • 接口加密/签名:获取数据的接口,要求请求里必须带一个根据时间、参数算出来的特殊“签名”(token),而且这个签名算法可以经常变。爬虫不知道算法就构造不出合法请求。

2.3 观察用户“行为”

  • 模拟真人操作:在网页里埋点,记录用户怎么操作:鼠标怎么移动、页面滚动多快、在哪些地方点了、页面停留多久。真人的操作是带点随机、有停顿、有重点区域的。

  • 识别机器人模式:如果一个“用户”总是在固定位置瞬间点击、页面滚得飞快、停留时间精确一致,那大概率是机器。发现这种模式,就限制或者封禁。

2.4 提高爬取成本

  • 限制账号/IP:如果一个账号/IP短时间内请求太多数据,直接封号或者限制访问频率。爬虫就得准备海量账号和代理IP,成本大大增加。

  • 返回假数据/干扰数据:对于高度怀疑的爬虫,可以返回一些错误或者无意义的数据,干扰他们的数据收集。

  • 法律手段:在用户协议里明确禁止爬虫,发现大规模恶意爬取,收集证据走法律途径。

核心思想:不是为了完全挡死(那也会影响正常用户),而是让爬数据的成本(时间、金钱、精力)远高于数据本身的价值,对方觉得不划算自然就放弃了。

3. MySQL使用中要注意些什么?

3.1 索引方面

  • 一定要建索引:WHERE条件里经常出现的列、JOIN连接的列、ORDER BY/GROUP BY的列,必须考虑建索引。没索引查大表就是灾难。

  • 建对索引:多个列一起查(比如按省+市查),建一个(province, city)的组合索引,比分开建两个单列索引效果好得多。注意组合索引的顺序(最常用的放左边)。

  • 不要在索引列上做计算或函数操作(WHERE YEAR(date) = 2023 会让date索引失效,改成 WHERE date BETWEEN '2023-01-01' AND '2023-12-31')。

  • 模糊查询 LIKE 尽量别用 %开头(LIKE '%keyword' 索引失效)。

  • 注意数据类型,字符串列用数字查(WHERE string_col = 123)也可能失效。

  • 定期检查优化:用 EXPLAIN 命令看看你的SQL语句到底有没有用索引,怎么用的。删掉没用的索引(索引也占空间,更新数据时还会拖慢速度)。

3.2 查询方面

  • 别用 SELECT *:只取你需要的列。尤其是大表,SELECT * 会拉取大量不必要的数据,浪费带宽和内存。

  • 小心 JOIN:JOIN 多个大表很危险。确保连接条件列有索引。尽量用小表去驱动大表(比如 FROM small_table JOIN big_table)。

  • 优化分页:LIMIT 1000000, 20 这种深度分页会扫描大量数据,非常慢。改成 WHERE id > 1000000 ORDER BY id LIMIT 20(前提是有自增ID索引)。

  • 避免大事务:一个事务里操作太多数据(比如更新百万行)会锁很久,阻塞别人。能拆就拆成小事务。

3.3 插入方面

  • 千万避免逐条插入:INSERT INTO table VALUES (...); INSERT INTO table VALUES (...),特别是循环里一条条插。

  • 批量插入才对:一次插入多条 INSERT INTO table VALUES (...), (...), (...);。一次插几百几千条,速度能提升几十上百倍。

  • 文件导入更高效:如果数据已经在文件里(如CSV),用 LOAD DATA INFILE 命令导入,速度比 INSERT 快非常多。

3.4 维护方面

  • 监控慢查询:开启慢查询日志,定期分析哪些SQL慢了,然后去优化它。

  • 注意锁:理解行锁、表锁、死锁。写操作会锁数据,尽量避免长事务和大范围更新。遇到死锁(MySQL错误1213),代码里要有重试机制。

  • 合理配置:根据服务器内存大小,设置好关键的缓存参数(如 innodb_buffer_pool_size),让常用数据尽量在内存里。

一句话经验:建对索引 + 批量操作 + 避免大事务和慢查询,能解决大部分MySQL性能问题。


4. 数据量大、爬取频率高,怎么防止入库堵死?

绝对不能 “来一条插一条”,想象一下高峰期地铁站,如果一个人来了就开门放进去,闸机瞬间就挤爆了。数据库也一样,一条条插扛不住高并发写入。这是最要避免的。

4.1 本地内存队列

  • 爬虫程序爬到的数据,先不急着写数据库。在爬虫程序自己内部搞一个“待办事项清单”(内存队列),把数据先临时放在这个清单里。

  • 比如Python可以用 list 或者 queue.Queue。攒够一定数量(比如1000条)或者等一小段时间(比如5秒),再一次性处理这批数据。

4.2 Redis高速缓冲队列

 爬虫:只管“投递”到 Redis

  • 爬虫程序成功抓到一条数据后,不做任何数据库操作。

  • 它唯一要做的,就是把这条数据(通常是 JSON 字符串或者序列化后的对象)作为一个“包裹”,快速塞进 Redis 的队列(List)里。

  • 使用 Redis 的lpush命令:

    # 爬虫代码片段
    import redis
    import json
    
    r = redis.Redis(host='localhost', port=6379, db=0)  # 连接Redis
    crawled_data = {'title': '...', 'url': '...', 'content': '...'}  # 爬取到的一条数据
    
    # 核心动作:将数据JSON化,推入名为 'crawl_data_queue' 的Redis List 的左侧
    r.lpush('crawl_data_queue', json.dumps(crawled_data))

Redis:安心当“大仓库”

  • 这个名叫 crawl_data_queue 的 Redis List 结构,就是我们的中转仓库。

  • 优点:

    • 内存速度:写入/读取极快,轻松应对爬虫的高并发写入。

    • 缓冲能力:即使爬虫瞬间爆发式产生大量数据,Redis 也能先存下来(只要内存足够),避免了爬虫被阻塞或数据丢失(相比内存队列更可靠)。

    • 持久化(可选):可以配置 Redis 的 RDB 或 AOF 持久化,即使 Redis 重启,大部分数据也能恢复(比纯内存队列更安全)。

    • 解耦:爬虫程序和入库程序完全分离,互不影响。爬虫挂了,数据还在 Redis 里;入库程序维护,爬虫照常工作。

入库程序(Worker):批量“搬运”进数据库

  • 这是一个独立运行的后台程序(或者多个程序并行工作),它的职责只有一个:定时或定量地从 Redis 仓库里“取件”,攒够一批后一次性写入数据库。

  • 工作流程:

    • 批量取件:使用 Redis 的 RPOP (Right Pop) 或 BRPOP (Blocking Right Pop) 命令,从队列右侧取数据(保证先进先出)。一次可以取多条(比如 100-1000 条)。

      # 入库Worker代码片段
      batch_size = 500  # 一次批量处理500条
      while True:
          # 从 'crawl_data_queue' 右侧阻塞取出最多 batch_size 条数据,最多等5秒
          # BRPOP 返回 (list_name, item) 元组列表
          data_items = r.brpop('crawl_data_queue', timeout=5, count=batch_size)
      
          if not data_items:  # 超时没取到数据,可能队列暂时空
              time.sleep(1)   # 稍作休息再试
              continue
      
          # 解析取出的JSON数据
          parsed_data = [json.loads(item[1]) for item in data_items]  # item[1]是数据内容
    • 构建批量SQL:将取出的这批数据,拼接成一条高效的 INSERT 语句。

      # 假设要插入到 'articles' 表 (title, url, content)
      placeholders = ','.join(['(%s, %s, %s)'] * len(parsed_data))
      sql = f"INSERT INTO articles (title, url, content) VALUES {placeholders}"
      
      # 把数据展平成一个长列表作为参数
      params = []
      for item in parsed_data:
          params.extend([item['title'], item['url'], item['content']])
      
      # 执行批量插入
      cursor.execute(sql, params)
      db.commit()  # 提交事务
    • 循环往复:Worker 不断重复 取件 -> 攒批 -> 批量插入 的过程。

需要注意的点

  • Redis 内存监控:这是核心!必须监控 Redis 的内存使用情况。如果队列持续增长超过内存容量,Redis 会崩溃或根据配置驱逐数据(可能丢数据)。要设置警报,并在内存达到阈值时:

    • 增加 Redis 内存或搭建集群。

    • 增加 Worker 数量和处理能力。

    • 必要时临时降级或暂停爬虫。

  • Worker 健壮性:Worker 程序要稳定,做好错误处理(数据库连接失败、SQL 错误、单条数据格式错误等),避免 Worker 频繁崩溃。

  • 数据格式:约定好爬虫写入和 Worker 读取的数据格式(如 JSON Schema),避免解析错误。

  • 队列积压监控:监控 crawl_data_queue 的长度(LLEN 命令)。持续积压说明 Worker 处理能力不足或数据库写入慢了。

总结一下用 Redis 做缓冲的核心流程:

  • 爬虫:抓数据 -> LPUSH 塞进 Redis List。

  • Redis:存着数据,等着被取走。

  • 入库 Worker:BRPOP 批量取数据 -> 拼接 SQL -> 批量插入数据库 -> 重复。

你可能感兴趣的:(Python/爬虫学习记录-Day05)