Python 爬虫入门:从抓取源码到模拟登录 (附代码详解)

186 阅读11分钟

Python 爬虫入门:从抓取源码到模拟登录 (附代码详解)

你好,未来的爬虫工程师!

你是否曾惊叹于某些应用能聚合来自不同网站的信息?或者想自动从网页上收集数据用于分析、研究或仅仅是满足好奇心?这一切的背后,往往离不开一种强大的技术——网络爬虫 (Web Scraping)

Python 因其丰富的库和简洁的语法,成为了开发网络爬虫的首选语言之一。这篇博客将带你一步步走进 Python 爬虫的世界,从最基础的网页抓取,到应对反爬虫策略,再到处理多页数据和模拟登录,让你对爬虫开发有一个全面的认识。

准备好了吗?让我们开始这段激动人心的旅程吧!

1. 第一步:获取网页源码与 Beautiful Soup 解析

万丈高楼平地起,爬虫的第一步就是获取目标网页的“骨架”——HTML 源代码。Python 的 requests 库能轻松模拟浏览器发送 HTTP 请求,拿到网页内容。

但拿到原始 HTML 还不够,我们需要从中提取有用的信息。这时,强大的 Beautiful Soup 4 (bs4) 库就派上用场了!它可以将复杂的 HTML 文档转换成 Python 对象,让我们像操作本地文件一样方便地查找标签、提取文本和属性。

核心概念:

  • requests.get(url): 发送 GET 请求获取网页。
  • response.text: 获取响应的文本内容(HTML 源码)。
  • BeautifulSoup(html, 'html.parser'): 创建 Beautiful Soup 对象来解析 HTML。
  • soup.find('tag_name', attribute='value'): 查找第一个符合条件的标签。
  • soup.find_all('tag_name', class_='some_class'): 查找所有符合条件的标签。
  • .get_text(strip=True): 获取标签内的文本,strip=True 去除多余空白。

抓取名言警句

import requests
from bs4 import BeautifulSoup
import csv

# 目标网站 (一个练习用的网站)
url = 'http://quotes.toscrape.com/'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'} # 伪装一下浏览器

try:
    print(f"开始抓取: {url}")
    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status() # 检查请求是否成功
    response.encoding = response.apparent_encoding # 自动设置编码

    html_content = response.text

    # 使用 Beautiful Soup 解析
    soup = BeautifulSoup(html_content, 'html.parser')

    quotes_data = []
    quote_divs = soup.find_all('div', class_='quote') # 找到所有包含名言的 div

    print(f"找到 {len(quote_divs)} 条名言,开始解析...")
    for quote_div in quote_divs:
        text = quote_div.find('span', class_='text').get_text(strip=True)
        author = quote_div.find('small', class_='author').get_text(strip=True)
        tags = ', '.join([tag.get_text(strip=True) for tag in quote_div.find_all('a', class_='tag')])
        quotes_data.append({'text': text, 'author': author, 'tags': tags})
        print(f"  - {text} - {author}")

    # 保存到 CSV 文件
    if quotes_data:
        filename = 'quotes.csv'
        with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
            writer = csv.DictWriter(f, fieldnames=['text', 'author', 'tags'])
            writer.writeheader()
            writer.writerows(quotes_data)
        print(f"\n数据已保存到 {filename}")

except requests.exceptions.RequestException as e:
    print(f"抓取出错: {e}")
except Exception as e:
    print(f"处理出错: {e}")

代码解释:

  1. 我们使用 requests.get 获取网页,并传入 headers 伪装成浏览器。
  2. response.raise_for_status() 确保我们得到了成功的响应 (状态码 200 OK)。
  3. BeautifulSoup 解析 HTML。
  4. soup.find_all('div', class_='quote') 定位到所有包含名言信息的 div 块。
  5. 在每个 div 块内部,我们继续使用 find 找到具体的 span (文本) 和 small (作者) 标签,并用 get_text() 提取内容。
  6. 最后,使用 csv 模块将提取到的数据写入 CSV 文件,方便后续使用。

2. 升级装备:爬虫伪装术

直接用 Python 脚本访问网站,很容易被识别为“机器人”。为了让我们的爬虫更像一个普通用户,我们需要一些伪装技巧:

  • 浏览器伪装 (User-Agent): 在请求头 (Headers) 中加入 User-Agent 字段,告诉服务器“我是一个正常的浏览器”。你可以在浏览器的开发者工具 (F12) -> Network -> Headers 中找到你自己的 User-Agent。
  • 设置时间间隔 (Rate Limiting): 别像机关枪一样疯狂请求!在每次请求之间加入 time.sleep(秒数),模拟人类的浏览速度,减轻服务器压力,也降低被封 IP 的风险。
  • 代理 IP (Proxy): 如果你的 IP 因为请求过于频繁被封了,或者你想模拟不同地区的访问,可以使用代理 IP。requests 库支持通过 proxies 参数使用代理。

** 添加延迟和代理结构:**

import requests
import time

headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
}

# 代理 IP (需要替换为有效的代理地址)
proxies = {
  'http': 'http://YOUR_PROXY_IP:PORT',
  'https': 'http://YOUR_PROXY_IP:PORT',
}

url = 'http://quotes.toscrape.com/page/2/' # 尝试访问第二页

try:
    print("等待 1.5 秒...")
    time.sleep(1.5) # 请求前暂停

    # 发送请求,带上 Headers,如果需要代理,加上 proxies=proxies
    # response = requests.get(url, headers=headers, proxies=proxies, timeout=15)
    response = requests.get(url, headers=headers, timeout=10) # 这里暂时不用代理

    response.raise_for_status()
    print(f"成功访问 (带伪装): {url}")
    # ... 后续解析代码 ...

except requests.exceptions.ProxyError as e:
    print(f"代理错误: {e}")
except requests.exceptions.RequestException as e:
    print(f"请求错误: {e}")

关键点: 伪装是爬虫与反爬策略博弈的第一步,让你的爬虫活得更久一点。

3. 应对反爬虫策略

网站方为了保护数据和服务器,会设置各种“关卡”来阻止爬虫,这就是反爬虫。常见的策略有:

  • User-Agent 检测: 拒绝非浏览器标识。(应对:伪装 User-Agent)
  • IP 频率限制/封禁: 单位时间内请求次数过多。(应对:加延迟 time.sleep(),使用代理 IP 池)
  • 验证码 (CAPTCHA): 需要人机交互验证。(应对:难点!可能需要接入第三方打码平台,或尝试 OCR 识别,或寻找无需验证码的接口)
  • 动态加载 (JavaScript/AJAX): 数据由 JS 在浏览器生成,直接看 HTML 源码可能不全。(应对:分析浏览器 F12 Network 中的 XHR/Fetch 请求找到数据接口,或使用 Selenium/Playwright 模拟浏览器行为)
  • 登录验证: 核心数据需要登录。(应对:模拟登录,见第 5 点)
  • Headers 检测: 检查 Referer, Cookie 等。(应对:尽量模拟完整请求头)

应对核心:

  • 像人一样访问: 合理的频率、完整的 Headers。
  • 遵守规则: 查看网站的 robots.txt 文件 (通常在网站根目录,如 www.example.com/robots.txt),了解网站允许爬取的范围。
  • 仔细分析: 打开浏览器开发者工具 (F12),切换到 Network (网络) 标签页,观察页面加载时发出了哪些请求,特别是 XHR/Fetch 类型的请求,它们往往是获取动态数据的关键。

示例:找到数据接口 (概念)

如果你发现数据是通过 AJAX 加载的,可以尝试直接请求那个接口:

# 假设通过 F12 发现数据接口 URL
api_url = 'http://example.com/api/get_data?category=news'
headers = {'User-Agent': '...', 'X-Requested-With': 'XMLHttpRequest'} # 有些 API 需要这个

try:
    response = requests.get(api_url, headers=headers)
    response.raise_for_status()
    json_data = response.json() # API 通常返回 JSON
    print("成功从 API 获取数据:", json_data)
    # ... 处理 json_data ...
except requests.exceptions.RequestException as e:
    print(f"请求 API 失败: {e}")
except requests.exceptions.JSONDecodeError:
    print("解析 JSON 失败")

示例:使用 Selenium (概念)

对于复杂的 JS 渲染页面,Selenium 可以驱动一个真实的浏览器来加载页面:

# 需要安装 selenium 和 webdriver_manager
# pip install selenium webdriver-manager

# from selenium import webdriver
# from selenium.webdriver.common.by import By
# from selenium.webdriver.chrome.service import Service
# from webdriver_manager.chrome import ChromeDriverManager
# import time

# options = webdriver.ChromeOptions()
# options.add_argument('--headless') # 无头模式运行
# service = Service(ChromeDriverManager().install())
# driver = webdriver.Chrome(service=service, options=options)

# try:
#     driver.get("一个 JS 渲染的复杂页面 URL")
#     time.sleep(3) # 等待 JS 执行完毕 (实际应用中用更智能的等待)
#     content = driver.find_element(By.ID, 'dynamic-content-id').text
#     print("Selenium 获取到的内容:", content)
# finally:
#     driver.quit()

记住: 反爬虫技术日新月异,没有一劳永逸的方法,持续学习和分析是关键。

4. 深入探索:单页与多页数据爬取

网站的内容通常不会放在一个页面上。我们需要让爬虫能够“翻页”或“点击链接”来获取更多数据。

  • 页码变更: 观察列表页 URL 的变化规律。通常是 ?page=2, &p=3 这样的参数,或者 /list/page/2/ 这样的路径。找到规律后,用循环生成不同的 URL 即可。
  • 超链接跟随: 从当前页面提取所有指向目标页面(如详情页)的链接 (<a> 标签的 href 属性),然后依次访问这些链接。注意处理相对路径 (/details/123) 和绝对路径 (http://...),urllib.parse.urljoin 可以帮你拼接。

爬取多个分页

import requests
from bs4 import BeautifulSoup
import time
from urllib.parse import urljoin

base_url = 'http://quotes.toscrape.com'
headers = {'User-Agent': '...'} # 保持 User-Agent

all_quotes = []
max_pages_to_crawl = 3 # 设置最大爬取页数

print("开始爬取多个分页...")
current_url = base_url + '/'

for page_num in range(1, max_pages_to_crawl + 1):
    print(f"--- 正在处理页面: {current_url} ---")
    try:
        time.sleep(1) # 友好访问
        response = requests.get(current_url, headers=headers, timeout=10)
        response.raise_for_status()
        response.encoding = response.apparent_encoding
        soup = BeautifulSoup(response.text, 'html.parser')

        # 提取当前页数据 (同第一部分)
        quote_divs = soup.find_all('div', class_='quote')
        for quote_div in quote_divs:
            text = quote_div.find('span', class_='text').get_text(strip=True)
            author = quote_div.find('small', class_='author').get_text(strip=True)
            all_quotes.append({'text': text, 'author': author})
            print(f"  抓取到: {text[:30]}... by {author}")

        # 查找“下一页”的链接
        next_li = soup.find('li', class_='next')
        if next_li and next_li.find('a'):
            next_page_relative_url = next_li.find('a')['href']
            current_url = urljoin(base_url, next_page_relative_url) # 拼接成完整的下一页 URL
        else:
            print("找不到下一页,停止爬取。")
            break # 没有下一页了,退出循环

    except requests.exceptions.RequestException as e:
        print(f"请求页面 {current_url} 失败: {e}")
        break
    except Exception as e:
        print(f"处理页面 {current_url} 时出错: {e}")

print(f"\n多页爬取完成,共获取 {len(all_quotes)} 条名言。")

代码解释:

  1. 我们用一个 for 循环(或 while 循环)来控制爬取的页数。
  2. 在每次循环中,请求当前页 current_url
  3. 解析数据后,使用 soup.find('li', class_='next') 找到包含“下一页”链接的 <li> 元素。
  4. 如果找到了,提取 <a> 标签的 href 属性(这通常是相对路径)。
  5. 使用 urljoin(base_url, next_page_relative_url) 将基础 URL 和相对路径拼接成下一页的完整 URL,更新 current_url,准备下一次循环。
  6. 如果找不到“下一页”按钮,说明到了最后一页,break 退出循环。

5. 高级挑战:模拟登录 (仅用于学习)

郑重声明:模拟登录爬取 QQ、微博等大型社交网站数据,不仅技术难度极高,而且极有可能违反用户协议,导致账号被封禁,甚至可能涉及法律风险。本节仅作技术原理探讨,请勿用于非法用途!

有些数据需要登录后才能看到。模拟登录的核心是让服务器认为你的爬虫是一个已经登录的用户。

  • 原理: 登录通常是向服务器发送一个 POST 请求,包含用户名、密码等信息。成功后,服务器会在响应中设置 Cookie(一小段身份信息)。后续访问其他页面时,带上这个 Cookie,服务器就知道你是谁了。
  • requests.Session 对象: 它是 requests 库的利器!Session 对象会自动管理 Cookie。你只需要用同一个 Session 对象发送所有请求(包括登录请求和后续的数据请求),它就能自动帮你处理 Cookie 的传递。
  • 挑战:
    • 找到登录接口和参数: F12大法好!监控登录过程的网络请求,找到那个 POST 请求,看清它的 URL 和表单数据 (Form Data)。
    • CSRF Token: 很多网站有这个安全令牌,通常在登录页的隐藏字段里,需要提取出来一起提交。
    • 验证码: 图形、滑块... 非常棘手。
    • JS 加密: 密码等信息可能在提交前被 JS 加密,需要逆向分析 JS 代码。

示例代码 (极其简化的概念模型):

import requests
from bs4 import BeautifulSoup

# 使用 Session 对象自动管理 Cookie
session = requests.Session()
session.headers.update({'User-Agent': '...'})

login_page_url = 'http://a-simple-login-site.com/login' # 假设的登录页
login_api_url = 'http://a-simple-login-site.com/do_login' # 假设的登录接口
protected_url = 'http://a-simple-login-site.com/dashboard' # 假设的登录后页面

try:
    # 1. 访问登录页,获取 CSRF token (假设有)
    print(f"访问登录页: {login_page_url}")
    resp_page = session.get(login_page_url)
    resp_page.raise_for_status()
    soup_page = BeautifulSoup(resp_page.text, 'html.parser')
    csrf_token = soup_page.find('input', {'name': 'csrf_token'})['value'] # 假设是这样获取
    print(f"获取到 CSRF Token: {csrf_token}")

    # 2. 准备登录数据
    login_data = {
        'username': 'my_username',
        'password': 'my_password',
        'csrf_token': csrf_token,
        # ... 可能还有其他字段
    }

    # 3. 发送 POST 请求进行登录
    print(f"发送登录请求到: {login_api_url}")
    resp_login = session.post(login_api_url, data=login_data)
    resp_login.raise_for_status()

    # 4. 检查登录是否成功 (检查方式因网站而异)
    if "登录成功" in resp_login.text or resp_login.url == protected_url:
        print("登录成功!")

        # 5. 访问需要登录的页面
        print(f"访问受保护页面: {protected_url}")
        resp_protected = session.get(protected_url)
        resp_protected.raise_for_status()
        print("成功获取受保护页面内容:")
        # print(resp_protected.text[:200] + "...") # 可以打印部分内容看看
        # ... 解析受保护页面的数据 ...
    else:
        print("登录失败,请检查。")
        # print(resp_login.text) # 打印失败信息

except requests.exceptions.RequestException as e:
    print(f"登录过程中出错: {e}")
except Exception as e:
    print(f"发生错误: {e}") # 比如找不到 csrf token

再次强调: 模拟登录复杂且敏感,请务必在法律和道德允许的范围内,以学习为目的进行尝试。对于大型网站,优先考虑是否有官方 API。

总结与忠告

恭喜你!你已经了解了 Python 爬虫从入门到进阶的关键技术点:

  1. 获取与解析: requests + BeautifulSoup 是基础。
  2. 伪装与反反爬: User-Agent, time.sleep, 代理 IP,以及分析动态加载是进阶。
  3. 多页处理: 掌握 URL 规律和链接提取。
  4. 模拟登录: 使用 requests.Session,但要极其谨慎。

最后,请务必记住:

  • 做负责任的爬虫开发者:
    • 遵守 robots.txt 协议。
    • 控制爬取频率, 不要给目标网站带来过大负担。
    • 表明身份 (User-Agent), 如果可能。
  • 尊重数据版权和隐私: 不要爬取、使用、传播受保护或私密的数据。
  • 遵守法律法规和网站条款: 非法爬取可能带来严重后果。

网络爬虫是一个强大而有趣的工具,希望这篇博客能为你打开一扇新的大门。不断实践,不断学习,你也能成为一名出色的爬虫工程师!

Happy Scraping!