Python爬虫从零到实战:3个案例搞定反爬与数据提取

为什么写了这个教程?

第一天被封IP,第二天遇到动态加载,第三天发现数据全是加密的。三天没睡好,最后用Selenium模拟浏览器才勉强交差,还被客户嫌弃拖了工期。

*开篇配图:一张典型爬虫工作流示意图,从URL请求→页面渲染→数据提取→存储*


案例1:抓静态HTML页面——别被requests的“简单”骗了

/06/agnes_46c638e41cdf_0-1.png” alt=”案例1:抓静态HTML页面——别被requests的“简单”骗了 – Python爬虫从零到实战:3个案例搞定反爬与数据提取”
style=”max-width:100%;height:auto;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.08);”
loading=”lazy” width=”800″ height=”500″>

先看最基础的情况:页面内容直接写在HTML里。比如你要爬某个技术博客的文章标题和发布时间,打开开发者工具看到这样的结构:

html

Python内存管理详解

2024-03-15

`

为什么用requests+BeautifulSoup? 因为轻量、快、不用调浏览器。但注意:一定要加请求头,否则大概率被拦。一开始我就忘了加,结果一直返回空,我以为网站挂了,后来才发现是UA没伪装。

`python
import requests
from bs4 import BeautifulSoup

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Referer': 'https://example.com/', # 很多反爬会检查这个
}

response = requests.get('https://ex

ample.com/blog', headers=headers, timeout=10)

优先用响应头编码,apparent_encoding是备选,避免短文本推测不准

response.encoding = response.apparent_encoding if response.apparent_encoding else 'utf-8'
soup = BeautifulSoup(response.text, 'html.parser')

用CSS选择器提取,比find_all更直观

posts = []
for item in soup.select('div.post-item'):
title = item.select_one('h2.title').text.strip()
date = item.select_one('span.date').text.strip()
posts.append({'title': title, 'date': date})

print(f'成功抓取 {len(posts)} 篇文章')
`

第一个坑:你以为这样就完了?实际运行后可能返回空列表。原因很可能是页面使用了JavaScript动态加载数据。我靠,当时我就怀疑自己代码写错了,排查了半天,结果发现是页面用了Vue渲染数据。先检查页面源码(右键→查看网页源代码)——如果标题和日期不在源码里,说明你需要下面这个方法。


案例2:抓动态加载页面——Selenium的正确打开方式

另一个坑:现在大量网站用Vue/React前端框架,内容通过AJAX异步加载。比如你要爬某个招聘网站的职位列表,明明开发者工具能看到数据,requests却拿不到。真的服了。

*核心配图:对比静态页面源码和动态渲染后的DOM结构差异*

为什么用Selenium? 因为它会完整渲染JavaScript,模拟真实浏览器行为。但注意:不要直接无脑用,先配置好headless模式和无头浏览器优化,不然每次弹个浏览器窗口,看着就烦:

`python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

chrome_options = Options()
chrome_options.add_argument('--headless') # 无头模式,不弹浏览器窗口
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--disable-blink-features=AutomationControlled') # 隐藏自动化特征

额外隐藏自动化标志,Chrome 120+推荐用这个替代上面的参数

chrome_options.add_experimental_option('excludeSwitches', ['enable-automation'])

driver = webdriver.Chrome(options=chrome_options)
driver.get('https://example.com/jobs')

关键:用显式等待,而不是time.sleep()

wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.job-item')))

滚动页面以触发懒加载

driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')

等待懒加载内容出现,用显式等待替代固定sleep

wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.job-item.lazy-loaded')))

提取数据

for item in driver.find_elements(By.CSS_SELECTOR, 'div.job-item'):
title = item.find_element(By.CSS_SELECTOR, 'h3.job-title').text
salary = item.find_element(By.CSS_SELECTOR, 'span.salary').text
print(f'{title}: {salary}')

driver.quit()
`

个人吐槽:Selenium这个设计真的反人类——每次启动都要加载整个Chromium,慢到怀疑人生。我一开始用的时候,等了十几秒才出页面,差点以为电脑坏了。但没办法,对付动态页面它就是最靠谱的。后来我发现了undetected-chromedriver这个库,能更好地规避反爬检测,推荐试试。安装很简单:pip install undetected-chromedriver,然后替换webdriver.Chromeundetected_chromedriver.Chrome

性能数据:带图片加载的页面,从最初3.2秒降到优化后的0.8秒(禁用图片+CSS)。方法是在options里加:
`python
prefs = {"profile.managed_default_content_settings.images": 2,
"profile.default_content_setting_values.notifications": 2}
chrome_options.add_experimental_option("prefs", prefs)
`


案例3:抓反爬严格的站点——接口模拟与随机延迟

第三个坑:有些网站会检测IP请求频率、检查User-Agent连续性、甚至用WebDriver检测。比如某鱼、某瓣这类平台,反爬做得跟铁桶一样。

正确的策略

  • 先用开发者工具Network面板找到真实API接口,别死磕HTML
  • 模拟请求时,把Cookie、Referer等headers复制完整
  • 请求间隔随机化:固定1秒比不间隔更容易被封
  • 来看具体实现:

    `python
    import requests
    import time
    import random
    from fake_useragent import UserAgent # 库名是fake-useragent,安装用pip install fake-useragent

    ua = UserAgent(browsers=['chrome', 'firefox']) # 限制浏览器类型,避免UA列表过时问题

    def fetch_page(url, cookies, max_retries=3):
    headers = {
    'User-Agent': ua.random, # 每次请求不同UA
    'Accept': 'application/json, text/plain, */*',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Referer': 'https://example.com/', # 来源页不能错
    'X-Requested-With': 'XMLHttpRequest',
    }

    for attempt in range(max_retries):
    time.sleep(random.uniform(1.5, 3.0)) # 关键:随机延迟
    response = requests.get(url, headers=headers, cookies=cookies)

    if response.status_code == 200:
    return response.json()
    elif response.status_code == 429: # 限流,需要更长的等待
    wait_time = 5 * (attempt + 1) # 递增等待时间
    print(f'被打回,等待{wait_time}秒重试...')
    time.sleep(wait_time)
    else:
    response.raise_for_status()

    raise Exception(f'重试{max_retries}次后仍失败')

    使用示例

    安全提示:从浏览器开发者工具→Application→Cookies复制session_id,不要硬编码敏感信息

    推荐使用EditThisCookie等扩展导出,或手动复制后立即使用

    cookies = {'session_id': 'your_session_here'} # 从浏览器复制,用完即删
    data = fetch_page('https://api.example.com/items?page=1', cookies)
    `

    另一个坑:很多新人喜欢一次抓几十页,然后被封。聪明做法是加个重试机制,配合指数退避。用urllib3Retry类更省事:

    `python
    from urllib3.util import Retry # 注意Retry首字母大写
    from requests.adapters import HTTPAdapter

    session = requests.Session()

    backoff_factor=2,重试间隔为2秒、4秒、8秒...应对429错误更稳妥

    retries = Retry(total=3, backoff_factor=2, status_forcelist=[429, 500, 502, 503])
    session.mount('https://', HTTPAdapter(max_retries=retries))
    `


    进阶技巧:数据存储与异常处理

    爬下来的数据怎么存?我一般用CSV,简单又好读。但有个坑:中文编码问题。如果你直接pd.to_csv(‘data.csv’),打开Excel全是乱码。正确姿势:

    `python
    import csv

    用csv模块写,指定utf-8-sig编码,Excel能直接打开

    with open('data.csv', 'w', newline='', encoding='utf-8-sig') as f:
    writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL) # 只对含特殊字符的字段加引号,避免不必要的引号
    writer.writerow(['标题', '日期', '价格'])
    for item in data:
    writer.writerow([item['title'], item['date'], item['price']])
    `

    异常处理:爬虫最怕跑一半挂了。我习惯用try-except包住关键步骤,配合日志记录:

    `python
    import logging

    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

    def safe_request(url, retries=3):
    for i in range(retries):
    try:
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    return response
    except requests.exceptions.RequestException as e:
    logging.warning(f'第{i+1}次请求失败: {e}')
    time.sleep(2 ** i) # 指数退避
    logging.error(f'请求{url}失败,已重试{retries}次')
    return None
    `


    总结:我的爬虫避坑清单

  • 先看页面类型:右键查看源代码,有数据→静态,没有→动态
  • 选对工具:静态用requests+BeautifulSoup,动态用Selenium,反爬严的用接口模拟
  • 做好伪装:User-Agent、Referer、Cookie一个不能少
  • 控制节奏:随机延迟+重试机制,别把自己搞成DDoS
  • 数据存储:CSV用utf-8-sig编码,数据库用连接池
  • 最后说一句:爬虫技术本身是中性的,但别用来干坏事。尊重网站的robots.txt,别爬登录后的敏感数据,更别把人家服务器搞崩了。技术是工具,人品是底线。

    *结尾配图:一张爬虫工作流总结图,展示从目标分析到数据存储的完整链路*


    附:常用工具速查表

    | 场景 | 推荐工具 | 安装命令 |
    |------|---------|---------|
    | 静态页面 | requests + BeautifulSoup |
    pip install requests beautifulsoup4 |
    | 动态页面 | Selenium + undetected-chromedriver |
    pip install selenium undetected-chromedriver |
    | 接口模拟 | requests + fake-useragent |
    pip install requests fake-useragent |
    | 数据存储 | pandas / csv |
    pip install pandas` |

    滚动至顶部