资讯详情

Python爬取163邮箱邮件:urllib、requests、selenium三种方案对比

📅 2026/10/5 1:24:45 | 华诺云谱 👁 阅读
Python爬取163邮箱邮件:urllib、requests、selenium三种方案对比
前两天有个朋友问我怎么用Python把163邮箱收件箱里的邮件标题和正文批量导出来想做个轻量的邮件归档工具。聊着聊着发现不少人的第一反应是去爬邮箱网页版而且会在 urllib、requests、selenium 之间纠结半天。这个问题其实很有代表性目标都是“拿到收件箱列表和邮件内容”但选不同工具写法和踩坑点完全不同。我把三种常见思路连同我实际用下来的体会一次性整理出来也顺便说清楚到底哪些场景适合爬网页哪些场景其实有更省事的官方协议可用。1. 先把思路理清楚163邮箱数据到底该怎么拿很多人一听说“爬邮箱”就默认要模拟登录网页版其实不一定。163邮箱除了网页端还支持 IMAP、POP3 这类标准邮件协议。如果你的目标只是“批量获取自己收件箱里的邮件”用imaplib拉取邮件列表和正文反而最稳定、最快也基本不会被网页反爬机制烦到。那为什么还要聊 urllib、requests、selenium 这三条路因为有时候你并不只是想拿原始邮件数据而是希望模拟用户在网页上的操作流程比如自动把某个文件夹里的邮件导出、分析网页版特有的交互逻辑或者你所在公司内网环境里有限制只能用网页端暴露出来的接口。这个时候“爬网页”就成了刚需。1.1 官方IMAP协议才是“正规军”先说一个很多新手不知道的点163邮箱登录网页版用的是账号密码但IMAP/POP3这类协议要求使用“授权码”而不是账号本身的登录密码。你需要先在163邮箱网页版的设置里开启IMAP/SMTP服务系统会生成一串授权码这串授权码专门给第三方客户端用。用Python的imaplib拉取收件箱列表基本流程是这样的import imaplib import email from email.header import decode_header # 连接IMAP服务器163邮箱的IMAP地址是 imap.163.com imap imaplib.IMAP4_SSL(imap.163.com, 993) imap.login(your_email163.com, your_auth_code) # 选择收件箱(UNSEEN)可以只看未读邮件 imap.select(INBOX) status, data imap.search(None, ALL) mail_ids data[0].split() # 取最近5封邮件的主题和发件人 for mid in mail_ids[-5:]: status, msg_data imap.fetch(mid, (RFC822)) msg email.message_from_bytes(msg_data[0][1]) subject decode_header(msg[Subject])[0] print(主题:, subject) print(发件人:, msg[From]) print(---) imap.logout()这才是最“正统”的拿邮件方式一次不需要写很复杂的解析代码。但如果你明确要学网页爬虫或者要模拟网页端操作下面三种方法才是重头戏。1.2 urllib、requests、selenium三条路线的定位我先把这三条路线的特点摆出来方便你对照自己的场景选路线依赖复杂度处理动态页面能力典型适用场景urllibPython自带库无第三方依赖弱只能拿到初始HTML学习HTTP原理、极简脚本requests需安装requests库中可调页面背后的JSON接口自己抓包分析后的高效抓取selenium需安装selenium和浏览器驱动强能真实渲染页面登录复杂、滑块验证、强交互页面一句话总结urllib 适合理解底层requests 是干活主力selenium 是“最后手段”。实际开发里requests用的最多selenium通常用来处理那些requests搞不定的复杂登录。2. urllib版本不引第三方库从零模拟邮箱网页请求用urllib爬163邮箱最大的价值不是效率高而是能让你把HTTP请求的细节彻底看清楚。很多用requests的人遇到问题后不知道Session是怎么一回事就是因为跳过了这个阶段。2.1 登录流程拆解与Cookie管理163邮箱网页登录本质上是一个HTTP交互过程你带着账号密码发出POST请求服务端校验通过后返回一组Cookie之后每次请求页面都要带着这组Cookie否则服务器不认你。用urllib做的时候必须自己维护Cookie。Python里对应的模块是http.cookiejar和urllib.request.HTTPCookieProcessor组合起来相当于一个迷你浏览器。我实际跑的时候发现直接访问https://mail.163.com/会先看到登录页但真实登录入口在某个内嵌页面或者iframe里前端还可能对密码做了加密处理。因此urllib版的第一步不是填数据而是先GET登录页把页面里的关键参数和登录URL解析出来再构造POST请求。一个简化的代码结构如下import urllib.request import urllib.parse import http.cookiejar # 创建CookieJar并绑定到opener cookie_jar http.cookiejar.CookieJar() opener urllib.request.build_opener( urllib.request.HTTPCookieProcessor(cookie_jar) ) # 伪装成正常浏览器 opener.addheaders [ (User-Agent, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36), (Referer, https://mail.163.com/), ] # 先GET登录页获取必要的参数 login_page opener.open(https://mail.163.com/) page_html login_page.read().decode(utf-8, ignore) # 这里建议用正则或HTMLParser提取登录入口URL、加密公钥等参数注意一点urllib默认不会管理重定向和Cookie以外的很多事遇到163这种经常跳转的站点一定要留意Referer字段很多反爬策略就是靠它判断请求来源的。我最初用urllib时因为漏了Referer连续几次都返回登录页。2.2 解析收件箱列表正则只是权宜之计登录成功后访问收件箱页面返回的HTML里确实能看到邮件列表但问题在于页面里塞了大量JavaScript和样式直接正则抠数据会很痛苦。我见过不少新手用正则去匹配邮件标题写出一堆容易漏数据的代码。其实更好的做法是绕过HTML直接找网页背后的JSON接口。163邮箱网页版在切换文件夹、翻页时会向后端发送类似funcmbox:listMessages的请求返回的是结构化JSON。用urllib请求这个接口再用json.loads解析比解析HTML稳定太多。import json import re # 假设已经登录拿到了sid会话ID sid 这里填登录后拿到的sid # 这是163邮箱网页版常见的收件箱列表接口实际以你抓包为准 list_url https://mail.163.com/js6/s?sid sid funcmbox:listMessages params { var: var mail_list, folder: INBOX, page: 1, pageSize: 20, } req urllib.request.Request(list_url, dataurllib.parse.urlencode(params).encode()) req.add_header(X-Requested-With, XMLHttpRequest) resp opener.open(req) data json.loads(resp.read().decode(utf-8, ignore)) for item in data.get(data, {}).get(list, []): print(item.get(subject), item.get(from), item.get(receivedTime))这样做的前提是先用浏览器开发者工具抓包确认163邮箱当前版本的接口地址和参数格式。这个接口不是一成不变的163改过很多次前端网上老代码经常失效所以“抓包”这个动作比代码本身更重要。2.3 urllib版完整示例的边界如果你只是临时跑一次上面的代码已经够用。但如果准备长期维护我不建议只依赖urllib原因很简单手写请求头、手动管理Cookie、手动解析跳转这些事都很容易出错。一旦163调整前端逻辑你的代码就得跟着大改。urllib版的另一个问题是不方便处理HTTPS证书、代理、重试这些细节真遇到网络波动脚本会直接崩。所以它更适合作为学习工具不适合作为生产级爬虫的底座。3. requests版本更顺手的会话管理和请求构造如果你已经理解了HTTP请求和Cookie是怎么回事下一步就换requests这是目前Python爬虫最常用的库。它把urllib里很多繁琐细节封装好了代码写起来舒服得多也更贴近人话。3.1 为什么我不建议从urllib手写Sessionrequests.Session这个对象最大的价值是自动保存Cookie。你只需要创建一次Session后续所有GET/POST请求都会自动附带之前拿到的Cookie。这个特性在做163邮箱这种需要登录的网站时能省掉大量重复代码。哪怕是同一个登录流程用requests写出来的代码量几乎可以少一半import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://mail.163.com/, }) # GET登录页拿参数 r session.get(https://mail.163.com/) print(r.status_code) # 之后POST登录信息 login_data { username: your_email163.com, password: your_password, # 其余参数从登录页抓包获得 } r session.post(https://mail.163.com/entry/cgi/ntesdoor, datalogin_data) print(r.url) # 登录成功后URL会发生变化如果你在登录这一步发现直接提交明文密码不行或者服务器总是返回“账号或密码错误”大概率是163前端对密码做了加密。这时候你需要从登录页相关的JavaScript里找到加密算法和公钥在Python侧模拟同样逻辑。requests本身不解决加密问题它只是帮你把请求发出去。3.2 模拟登录163邮箱的关键三步用requests模拟登录时我通常会分成三步走每一步都验证结果避免到最后才发现问题。第一步GET登录页拿到入口URL和必要的隐藏字段。163邮箱登录页有时候会内嵌一个iframe真实的表单在里面这时候直接对顶层URL提交表单是无效的。可以先用r.text看一看页面结构再决定请求哪个地址。第二步构造登录请求。这一步最关键的是要从前端JavaScript里找到密码加密逻辑。我遇到过的情况是密码字段实际提交的是一段RSA加密后的密文只传明文是不行的。如果你不想逆向JS最务实的选择是先用浏览器登录把登录成功后的Cookie复制到requests里使用。这种方式对学习HTTP请求够用但Cookie会过期长期跑还是得解决登录逻辑。第三步检测登录是否成功。登录成功后response里通常会出现sid这个会话标识后续很多页面URL都带着它。你可以用正则把它提取出来保存到变量里也可以直接从Cookie里找。总之不要盲目继续请求先打印一下登录后的页面标题或URL确认自己真的进来了。3.3 抓收件箱列表接口与邮件详情登录成功之后requests的价值就体现出来了163收件箱列表通常是通过异步接口加载的直接用requests请求这个接口拿到JSON然后轻松提取字段。我写一个简化版逻辑list_api https://mail.163.com/js6/s params { sid: sid, func: mbox:listMessages, folder: INBOX, page: 1, pageSize: 30, } headers { X-Requested-With: XMLHttpRequest, Referer: https://mail.163.com/js6/main.jsp?sid sid, } resp session.get(list_api, paramsparams, headersheaders) data resp.json() for mail in data[data][list]: print(mail[subject]) print(mail[from]) print(mail[receivedTime])如果你的目标还包括邮件正文步骤就再多一步从列表里拿到每封邮件的唯一标识再请求邮件详情接口或者直接构造一封邮件的URL然后解析正文HTML。解析HTML时我建议用BeautifulSoup别用正则。因为邮件正文是HTML结构正则很容易被各种嵌套标签搞崩溃。from bs4 import BeautifulSoup # 假设html_content是某封邮件的正文HTML soup BeautifulSoup(html_content, html.parser) text soup.get_text(separator\n, stripTrue) print(text)使用requests时还要注意一个限制如果请求频率太高服务器会返回429 Too Many Requests。我在测试时遇到过几次明明账号密码都对却突然拉不到数据一看响应状态码就是429。这说明短时间请求太多触发了限流解决方案很简单在循环里加随机延时。import time import random for page in range(1, 6): # 抓取每一页数据 ... # 随机延时1.5到3.5秒 time.sleep(random.uniform(1.5, 3.5))这个动作不是为了“绕过”什么而是为了不给服务器造成压力也是在合规范围内保持稳定抓取的必要手段。4. selenium版本面对强交互页面的兜底方案当requests也搞不定的时候就该上selenium了。selenium的真实身份是浏览器自动化测试工具它不像前两种那样直接发HTTP请求而是启动一个真实的浏览器模拟人的点击、输入、滚动操作。好处是前端怎么渲染它就能怎么拿数据坏处是慢、吃资源、代码维护成本高。4.1 什么时候才需要上selenium我在实际项目里基本只有两种情况下会选selenium一是登录环节的加密参数太复杂用requests模拟登录的代价已经超过了selenium二是操作本身强依赖页面交互比如需要点击“加载更多”、拖动滚动条、切换文件夹用requests很难稳定复现。163邮箱网页版有时会出现滑块验证码这种验证码背后有很复杂的风险控制逻辑requests很难绕过。selenium虽然也需要人工介入或借助验证码识别但它至少可以先把页面渲染出来把最麻烦的登录流程稳住。我个人的建议是如果只是为了给自己的邮箱做归档就不要花大精力去对抗验证码直接IMAP最省心。但如果你想体验浏览器自动化的核心流程selenium这条线很值得走一遍。4.2 用selenium登录并保持登录态selenium操作163邮箱登录页有一个特别容易踩的坑登录框在iframe里面。如果你直接find_element_by_id(username)大概率找不到元素因为你的查找范围是整个页面而输入框其实在嵌套的iframe里。所以第一步要切换iframe。我常用的稳定写法是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 import time driver webdriver.Chrome() driver.get(https://mail.163.com/) # 等待iframe出现然后切换进去 wait WebDriverWait(driver, 10) iframe wait.until(EC.presence_of_element_located((By.TAG_NAME, iframe))) driver.switch_to.frame(iframe) # 输入账号密码 email_input wait.until(EC.presence_of_element_located((By.NAME, email))) email_input.send_keys(your_email163.com) driver.find_element(By.NAME, password).send_keys(your_password) driver.find_element(By.ID, dologin).click() # 切回主页面等待登录完成 driver.switch_to.default_content() time.sleep(3)登录后如果出现扫码或滑块验证我的处理方式很笨但很直接把程序停下来我手动滑一下然后继续。selenium的一个重要思路是“人机协作”不要指望所有环节都完全自动化尤其是验证码这种安全机制。等登录成功后可以调用driver.get_cookies()把Cookie保存到本地文件下次启动时直接加载省去反复登录的麻烦。4.3 获取收件箱列表和邮件正文登录成功后处理收件箱列表就有两种路线一是直接拿渲染后的HTML源码交给BeautifulSoup解析二是用selenium的定位API逐个找元素。前者代码更简洁后者更接近真实用户操作。我写一个定位元素的例子# 等收件箱列表加载出来 wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, .nui-msg-box))) mail_boxes driver.find_elements(By.CSS_SELECTOR, .nui-msg-box) for box in mail_boxes: sender box.find_element(By.CSS_SELECTOR, .nui-msg-sender).text subject box.find_element(By.CSS_SELECTOR, .nui-msg-subject).text time_text box.find_element(By.CSS_SELECTOR, .nui-msg-date).text print(sender, subject, time_text)这里需要提醒一句163邮箱前端改版频率比较高CSS类名不是保证不变的。如果你发现.nui-msg-box抓不到最简单的方法是在浏览器开发者工具里重新查看列表项的特征改成XPath定位。XPath的容错性通常更高比如按照文本内容定位# 查找所有包含邮件标题的节点 title_elements driver.find_elements(By.XPATH, //span[contains(class,subject)]) for elem in title_elements: print(elem.text)想要读取邮件正文可以先点击某条邮件然后在详情区域提取正文。如果邮件正文是在新窗口打开的还要注意切换窗口句柄。这个步骤看着简单实际处理起来细节非常多比如有的邮件是纯文本有的是HTML有的带附件你都得分别考虑。4.4 处理收件箱懒加载与翻页163邮箱的收件箱默认是分页显示的如果邮件很多就需要翻页或者滚动加载。selenium里模拟滚动加载最简单的方式是执行JavaScriptdriver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(2)如果需要点击“下一页”按钮建议优先通过WebDriverWait等待按钮可点击再执行click操作而不是死等固定时间。固定时间最大的问题是网络一波动脚本就失效。5. 常见问题与排查技巧实录写爬虫最难的不是写代码而是调试那些“理论上没问题但实际操作起来就崩”的细节。我把自己踩过的几个典型问题整理成了清单你遇到类似情况可以直接对号入座。5.1 登录失败、验证码和加密参数现象可能原因处理思路登录后还是回到登录页Cookie没保存成功检查是否使用了Session是否带上完整请求头明明密码正确却提示账号密码错误密码字段被前端加密分析登录JS获取加密算法和公钥触发滑块验证请求频率高或行为特征明显降低频率改用selenium人工确认selenium找不到登录框登录框在iframe里先switch_to.frame再定位5.2 429 Too Many Requests限流的应对这个错误在爬163邮箱时很常见尤其当你在循环里快速翻页时服务器会直接返回429 Too Many Requests响应体里往往还会带着类似last status: 429的提示。解决思路很简单放慢速度加入退避机制。比如连续请求失败时先等5秒、10秒、20秒按指数退避重试。import time retry 0 while retry 3: resp session.get(list_api, paramsparams, headersheaders) if resp.status_code 429: wait_time 5 * (2 ** retry) print(触发限流等待, wait_time, 秒) time.sleep(wait_time) retry 1 else: break很多人看到429就慌其实它和封号不同只是临时限制访问频率。只要你立刻停止高频率请求过一会儿就恢复了。切记不要用多线程、高并发去怼邮箱接口大概率会触发更严厉的风控。5.3 解析乱码和字段缺失163邮箱的页面编码有时候是UTF-8有时候接口返回的JSON又带各种转义字符。处理时我一般统一用resp.encoding utf-8或者直接交给resp.json()让requests自动处理。如果HTML页面出现乱码可以试resp.apparent_encoding。解析邮件正文时优先用BeautifulSoup这类成熟库而不是正则。字段缺失的问题一般出现在“列表页有数据但详情页没抓到正文”的情况。原因是邮件正文可能是异步加载的需要等页面完全渲染后再抓或者要额外请求正文接口。还有一个很隐蔽的问题163邮箱会选择性加载邮件特别是列表页只返回当前页数据别试图一次请求全部邮件按页循环才是正解。5.4 selenium定位不到元素的排查思路selenium定位不到元素95%的情况是时序问题也就是元素还没加载出来你就去查找了。标准解法是WebDriverWait配合expected_conditions不要用sleep硬等。另外注意页面是否存在多个iframe或者元素是否在Shadow DOM里。163邮箱的登录页就是典型的iframe嵌套如果不先切换定位就是失败的。如果是动态列表可能要等某个Ajax请求完成才能看到数据这时候可以用“等待某个元素文本出现”来替代固定等待。5.5 合规与账号安全提醒这里必须多说一句爬取邮箱服务不管用什么方法都应当只处理自己的账号数据并且遵守网站的用户协议和服务条款。不要用爬虫去批量抓取他人邮件不要干任何越权的事。合理控制请求频率、使用官方协议优先、账号开启授权码而不是明文密码保存这些都是底线。我在写这类脚本时还会特意把邮箱地址、授权码等敏感信息放到环境变量或配置文件中而不是硬编码在代码里防止代码传出去时泄露账号。6. 选型建议与个人实操体会如果把三种方法放到同一个项目里对比我最后的选型思路大概是这样的只是需要把邮件备份下来优先用imaplib半小时写完稳定又不触发风控。想学爬虫想理解HTTP会话、JSON接口、页面渲染这些概念用requests练手从163邮箱列表接口入手很合适。遇到滑块验证、复杂JS加密、需要模拟用户点击的强交互页面才上selenium而且要做好“慢”的心理准备。我自己在实际项目里requests用的最多因为只要抓包够仔细大部分登录和列表请求都能通过requests模拟出来。但遇到那种前端加密做得比较重的站点我也会毫不犹豫地切selenium毕竟时间成本比性能成本更值钱。最后分享一个经验无论你最后选哪种工具动手写代码之前先用浏览器开发者工具把登录请求和收件箱刷新请求的URL、参数、请求头看明白。这一步如果能做透后面至少能省下80%的调试时间。爬虫写得好不好很多时候不在于你会用哪个库而在于你对目标网站请求流程的理解有多深。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑