聊聊我用爬虫搞数据的一些实战经验

单元测试补习生
2026-07-02 11:34
阅读 594

说来也巧,写iOS写了快六年,从Swift刚出来那会儿就开始折腾,眼看着Objective-C一步步被"宣判死刑",自己也跟着从OC转Swift,再到后来搞SwiftUI,算是见证了苹果生态这几年的大变迁。坐标杭州,在滨江这边待过两家不大不小的公司,最近网易和阿里都有在聊,想着趁这段空窗期把最近学的一些东西整理整理。

其实吧,做iOS开发久了,你会发现很多技能是可以迁移的。比如最近我在搞AI相关的东西,搞着搞着就发现——数据从哪来?总不可能全靠手写吧?于是乎,爬虫这个技能点就被我点亮了。今天就来聊聊这段时间搞爬虫的一些实战心得,算是给自己做个复盘。

为什么一个iOS开发要去搞爬虫

事情是这样的。上个月我在研究一个AI小项目,想做一个基于真实数据的推荐系统demo。理想很丰满,现实很骨感——公开的数据集要么太老,要么根本不符合我的场景需求。

找数据找得我头都大了,跟大学时候写毕业论文那会儿有得一拼。后来一咬牙一跺脚:算了,自己爬吧。反正Python之前学过一点,又不是完全零基础。

现在想想,这个决定还挺对的。搞爬虫的过程让我对网络请求、数据解析、反爬策略这些底层逻辑有了更深的理解,回过头来看iOS里的网络层设计,反而有种"哦原来如此"的感觉。

第一次实战:从简单页面开始

我的第一个目标是爬某个技术社区的帖子数据。说实话,一开始真的想得太简单了。

import requests
from bs4 import BeautifulSoup

url = "https://example-tech-community.com/posts"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

posts = soup.find_all('div', class_='post-item')
for post in posts:
    title = post.find('h2').text
    print(title)

跑了一下,嗯,数据是拿到了。但当我想爬第二页、第三页的时候,问题来了——这个网站有反爬机制。

连续请求了几次之后,直接给我返回了403,IP被封了。当时那个心情,真的,就像线上出了P0事故一样,心凉了半截。

反爬对抗:一场没有硝烟的战争

被封IP之后,我开始了漫长的反爬研究。说实话,这段经历让我对"攻防"这件事有了全新的认识。

请求头伪装

最基础的一步,就是伪装请求头。很多网站的反爬其实很初级,就是看看你的User-Agent是不是正常的浏览器。

import random

USER_AGENTS = [
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
]

headers = {
    "User-Agent": random.choice(USER_AGENTS),
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "Referer": "https://www.google.com/",
}

这招对付一些初级反爬还是管用的。但稍微有点经验的网站,光靠这个远远不够。

IP代理池

IP被封之后,最直接的解决方案就是换IP。我搞了一个代理池,定时从免费的代理网站抓取可用代理,然后请求的时候随机切换。

import requests
import time

class ProxyPool:
    def __init__(self):
        self.proxies = []
        self.current_index = 0
    
    def add_proxy(self, proxy):
        self.proxies.append(proxy)
    
    def get_proxy(self):
        if not self.proxies:
            return None
        proxy = self.proxies[self.current_index]
        self.current_index = (self.current_index + 1) % len(self.proxies)
        return {"http": proxy, "https": proxy}

proxy_pool = ProxyPool()
# 假设这里添加了一些代理
proxy_pool.add_proxy("http://123.45.67.89:8080")
proxy_pool.add_proxy("http://98.76.54.32:3128")

def fetch_with_proxy(url):
    proxy = proxy_pool.get_proxy()
    try:
        response = requests.get(url, headers=headers, proxies=proxy, timeout=10)
        return response
    except Exception as e:
        print(f"请求失败: {e}")
        return None

但免费代理的质量你懂的,十个里面有九个是废的。后来我咬咬牙买了个付费代理服务,效果好了很多。这也算是花钱买教训吧。

请求频率控制

这个是我踩坑最深的一点。一开始我为了追求速度,并发开得贼猛,结果就是——被封得更惨了。

后来我学乖了,加了随机延时:

import time
import random

def polite_fetch(url):
    # 随机延时1-3秒
    time.sleep(random.uniform(1, 3))
    response = requests.get(url, headers=headers)
    return response

别小看这几秒钟的延时,它能让你的爬虫活得久很多。做爬虫跟做人一样,不能太激进,要懂得"可持续发展"。

动态页面的噩梦

如果说静态页面爬取是新手村,那动态页面就是第一个Boss。

现在很多网站都是前后端分离的,页面内容靠JavaScript动态渲染。你拿到的HTML源码里,可能只有一个空的<div id="app"></div>,数据全在JS里。

一开始我试图用正则去匹配JS里的数据,搞了两天,头发掉了一把,效果奇差。后来朋友推荐我试试Selenium。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def fetch_dynamic_page(url):
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')  # 无头模式
    options.add_argument('--disable-gpu')
    
    driver = webdriver.Chrome(options=options)
    try:
        driver.get(url)
        # 等待内容加载完成
        WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "content-loaded"))
        )
        page_source = driver.page_source
        return page_source
    finally:
        driver.quit()

Selenium确实能解决动态渲染的问题,但代价也很明显——慢。一个页面要等好几秒,如果要爬几千个页面,时间成本根本扛不住。

后来我又研究了一下,发现很多动态页面的数据其实是通过API接口获取的。打开浏览器的开发者工具,看看Network面板,找到那个返回JSON数据的接口,直接请求这个接口,比用Selenium渲染整个页面快得多。

这个思路的转变,让我想起了做iOS时处理网络请求的经验。很多时候,我们不需要关心UI怎么渲染,只需要关心数据从哪来、格式是什么。爬虫也是一样的道理。

数据清洗:脏活累活

爬到数据只是第一步,数据清洗才是真正耗时的地方。

爬下来的数据,格式五花八门:有的带HTML标签,有的有奇怪的换行符,有的字段缺失,有的数据重复……简直就像一个没有经过Code Review的代码库,惨不忍睹。

import re
import pandas as pd

def clean_data(raw_data):
    cleaned = []
    for item in raw_data:
        # 去除HTML标签
        text = re.sub(r'<[^>]+>', '', item.get('content', ''))
        # 去除多余空白
        text = re.sub(r'\s+', ' ', text).strip()
        # 过滤太短的内容
        if len(text) > 50:
            cleaned.append({
                'title': item.get('title', '').strip(),
                'content': text,
                'author': item.get('author', 'unknown'),
                'date': item.get('date', '')
            })
    return cleaned

# 去重
df = pd.DataFrame(cleaned_data)
df.drop_duplicates(subset=['title', 'content'], inplace=True)

说实话,数据清洗这块没什么技术含量,就是个体力活。但千万别小看它,数据质量直接决定了你后续分析或模型训练的效果。垃圾进,垃圾出,这话一点不假。

一些架构上的思考

搞了一段时间爬虫之后,我也在思考怎么把零散的脚本整合成一个相对完整的系统。毕竟,每次都要手动跑脚本、手动清洗数据,效率太低了。

我参考了一些开源项目的架构,设计了一个简单的爬虫框架:

模块 职责 技术选型
调度器 管理爬取任务、控制并发 Celery + Redis
下载器 负责HTTP请求、处理代理 aiohttp
解析器 解析HTML/JSON、提取数据 BeautifulSoup / jsonpath
存储器 数据持久化 MongoDB
监控 任务状态、异常告警 Prometheus + Grafana

用异步的方式来做下载器,效率提升非常明显。之前用requests同步爬,一分钟最多处理几十个页面;换成aiohttp异步之后,一分钟能处理几百个。

import aiohttp
import asyncio

async def fetch_async(url, semaphore):
    async with semaphore:
        async with aiohttp.ClientSession() as session:
            try:
                async with session.get(url, headers=headers, timeout=10) as response:
                    return await response.text()
            except Exception as e:
                print(f"异步请求失败: {e}")
                return None

async def main(urls):
    semaphore = asyncio.Semaphore(20)  # 控制并发数
    tasks = [fetch_async(url, semaphore) for url in urls]
    results = await asyncio.gather(*tasks)
    return results

# 运行
urls = ["https://example.com/page/1", "https://example.com/page/2", ...]
results = asyncio.run(main(urls))

这个架构跑起来之后,整体稳定性好了很多。之前脚本跑着跑着就挂了,现在有了任务队列和重试机制,即使某个请求失败也不会影响整体进度。

合规性:别忘了这条红线

最后想聊一个很多人容易忽略的问题——合规性。

爬虫这个东西,用好了是利器,用不好就是违法。之前不是有个案例嘛,某公司爬虫把人家服务器搞崩了,最后负责人进去了。

我给自己定了几条规矩:

  1. 遵守robots.txt:人家不让爬的,就别爬
  2. 控制请求频率:别把人家服务器搞崩了
  3. 不爬敏感数据:用户隐私、商业机密这些碰都别碰
  4. 数据用途合法:爬来的数据别拿去干违法的事

做技术的人,有时候容易沉浸在"我能做到"的快感里,而忽略了"我该不该做"的问题。这条红线,一定要守住。

写在最后

从iOS开发到搞爬虫,这个跨度看起来有点大,但其实底层的东西是相通的。网络请求、数据解析、并发控制、异常处理……这些概念在任何语言、任何领域都是通用的。

最近面试的时候,有面试官问我为什么一个iOS开发要学爬虫,我说:"技多不压身嘛,而且现在AI这么火,数据是AI的燃料,不懂数据怎么行?"

其实还有一个没说出口的原因——杭州这边的就业环境,纯iOS开发的岗位确实在缩减。不给自己多找几条路,万一哪天被优化了,连个退路都没有。

好了,今天就聊到这里。如果你也在搞爬虫,或者对AI相关的数据处理感兴趣,欢迎来交流。毕竟,一个人踩坑不如大家一起踩坑,对吧?

下次有机会再聊聊我是怎么用爬来的数据训练一个小模型的,那个故事就更精彩了(也更心酸)。

评论 0

最热最新
暂无评论
单元测试补习生Lv.1
0
影响力
0
文章
0
粉丝