Selenium爬取微博热搜完整实战:从环境搭建到反爬绕过的全流程踩坑指南
做爬虫这么多年,最头疼的就是动态网页。尤其是微博这种大厂的产品,反爬手段层出不穷,能把人逼疯。
前几天有个粉丝找我,说他用requests爬微博热搜,爬了半天只拿到一堆空标签。问我怎么回事。
我一看就笑了。现在的微博,早就不是当年那个静态页面的时代了。所有数据都是通过AJAX动态加载的,你直接请求HTML,能拿到数据才怪。
而且微博的反爬做得特别严,请求头稍微不对,Cookie过期,或者请求频率高一点,直接就给你弹验证码,甚至封IP。
很多人遇到这种情况,第一反应就是去抓包分析API。但微博的API参数都是加密的,而且经常变,你花了好几天破解出来,可能过一周就失效了。
这时候,Selenium就是你的救命稻草。
虽然Selenium速度慢,资源消耗大,但它有一个无可替代的优势:它是真正的浏览器。它能像真人一样渲染页面,执行JavaScript,处理各种动态内容。
只要你能在浏览器里看到的数据,Selenium就能拿到。
今天我就以微博热搜为例,给大家分享一个完整的Selenium爬虫实战。从环境搭建到反爬绕过,再到数据存储,一步一步带你写一个能稳定运行的爬虫。
为什么requests搞不定微博热搜?
在开始写代码之前,我们先搞清楚一个问题:为什么requests爬不到微博热搜的数据?
打开微博热搜页面,按F12查看网页源代码。你会发现,热搜列表的位置,只有一个空的div标签,里面什么内容都没有。
这是因为微博采用了前后端分离的架构。页面加载的时候,只加载一个空的HTML骨架,然后通过JavaScript向服务器发送AJAX请求,获取数据后再动态渲染到页面上。
requests只能获取到最初的HTML文档,不会执行里面的JavaScript代码,所以自然拿不到动态加载的数据。
而且微博的反爬措施非常完善:
- 请求头必须包含正确的User-Agent、Referer、Cookie等信息
- API参数有复杂的加密算法,且定期更新
- 对请求频率有严格的限制,短时间内大量请求会被封IP
- 会检测浏览器指纹,识别自动化工具
- 遇到异常请求会弹出验证码
如果你非要用requests去硬刚,那你需要花大量的时间去破解加密参数,维护Cookie池和代理池,而且随时可能失效。
而Selenium直接模拟真实浏览器的行为,完美避开了这些问题。
环境搭建:一步到位,告别驱动烦恼
很多人对Selenium望而却步,就是因为浏览器驱动的配置太麻烦了。
以前用Selenium,你需要手动下载对应浏览器版本的驱动,然后配置环境变量,或者在代码里指定驱动路径。只要浏览器一更新,驱动就失效了,非常烦人。
但从Selenium 4.6版本开始,这一切都成为了历史。
Selenium 4.6内置了Selenium Manager,会自动检测你电脑上安装的浏览器版本,然后自动下载对应的驱动。你什么都不用管,直接写代码就行。
这绝对是Selenium近年来最大的改进,没有之一。
环境搭建只需要两步:
- 安装Selenium库
pipinstallselenium==4.21.0- 安装Chrome浏览器(Edge也可以,代码几乎一样)
就这么简单。不需要下载任何驱动,不需要配置任何环境变量。
完整实战:手把手教你爬取微博热搜
这是我写的一个完整的微博热搜爬虫,经过多次优化,能稳定运行,基本不会被检测到。
先给大家看一下整体的流程图:
第一步:初始化浏览器并配置反爬参数
这是最关键的一步。如果你的反爬参数配置得不好,一打开页面就会被检测到是自动化工具。
fromseleniumimportwebdriverfromselenium.webdriver.chrome.optionsimportOptionsfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECimporttimeimportrandomimportcsvdefinit_browser():chrome_options=Options()# 基础反爬设置chrome_options.add_argument("--start-maximized")# 最大化窗口chrome_options.add_argument("--disable-blink-features=AutomationControlled")# 禁用自动化控制特征chrome_options.add_experimental_option("excludeSwitches",["enable-automation"])# 排除自动化开关chrome_options.add_experimental_option("useAutomationExtension",False)# 禁用自动化扩展# 隐藏浏览器指纹chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36")chrome_options.add_argument("--disable-extensions")# 禁用扩展chrome_options.add_argument("--disable-plugins-discovery")# 禁用插件发现chrome_options.add_argument("--disable-notifications")# 禁用通知# 无头模式(可选,后台运行)# chrome_options.add_argument("--headless=new")# 初始化浏览器driver=webdriver.Chrome(options=chrome_options)# 执行JavaScript,覆盖window.navigator.webdriver属性driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument",{"source":""" Object.defineProperty(navigator, 'webdriver', { get: () => undefined }) """})returndriver这些参数都是我踩了无数坑总结出来的。少了任何一个,都可能被微博检测到。
特别是disable-blink-features=AutomationControlled和覆盖navigator.webdriver这两个设置,是绕过Selenium检测的核心。
第二步:访问页面并等待元素加载
很多人写Selenium代码喜欢用time.sleep()来等待页面加载。这是一个非常不好的习惯。
time.sleep()会让程序固定等待一段时间,不管页面有没有加载完成。如果设置的时间太短,元素还没加载出来,就会报错;如果设置的时间太长,又会浪费时间。
正确的做法是使用显式等待。显式等待会一直等待,直到指定的元素加载出来,或者超时。
defcrawl_weibo_hot():driver=init_browser()hot_list=[]try:# 访问微博热搜页面driver.get("https://weibo.com/newlogin?tabtype=weibo&gid=102803&openLoginLayer=0&url=")# 显式等待热搜列表加载完成,最多等待10秒wait=WebDriverWait(driver,10)hot_items=wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,"div[class*='HotTopic_topic_']")))print(f"成功获取到{len(hot_items)}条热搜")# 随机等待1-2秒,模拟人类行为time.sleep(random.uniform(1,2))# 遍历每条热搜,提取数据forindex,iteminenumerate(hot_items,1):try:# 提取热搜标题title=item.find_element(By.CSS_SELECTOR,"h3[class*='HotTopic_title_']").text.strip()# 提取热搜热度heat=item.find_element(By.CSS_SELECTOR,"span[class*='HotTopic_num_']").text.strip()# 提取热搜标签(置顶、爆、新、热等)tag=""try:tag_element=item.find_element(By.CSS_SELECTOR,"span[class*='HotTopic_tag_']")tag=tag_element.text.strip()except:passhot_data={"排名":index,"标题":title,"热度":heat,"标签":tag,"爬取时间":time.strftime("%Y-%m-%d %H:%M:%S")}hot_list.append(hot_data)print(f"第{index}条:{title}[{heat}]{tag}")# 每条数据之间随机延迟0.5-1秒time.sleep(random.uniform(0.5,1))exceptExceptionase:print(f"提取第{index}条热搜失败:{e}")continueexceptExceptionase:print(f"爬取失败:{e}")finally:# 无论成功还是失败,都要关闭浏览器driver.quit()returnhot_list这里我用了CSS选择器来定位元素。相比XPath,CSS选择器更简洁,执行速度更快,而且不容易因为页面结构的微小变化而失效。
第三步:保存数据到CSV文件
爬取到数据后,我们把它保存到CSV文件里,方便后续分析。
defsave_to_csv(data,filename="weibo_hot.csv"):ifnotdata:print("没有数据可保存")return# 获取所有字段名fieldnames=data[0].keys()withopen(filename,"w",newline="",encoding="utf-8-sig")asf:writer=csv.DictWriter(f,fieldnames=fieldnames)writer.writeheader()writer.writerows(data)print(f"数据已保存到{filename}")if__name__=="__main__":print("开始爬取微博热搜...")hot_data=crawl_weibo_hot()save_to_csv(hot_data)print("爬取完成")注意这里我用了utf-8-sig编码,而不是utf-8。这样用Excel打开CSV文件的时候,中文就不会乱码了。
核心反爬技巧:让Selenium看起来更像真人
很多人用Selenium爬取微博,跑不了几次就被封了。这不是Selenium的问题,而是你没有做好反爬措施。
我总结了几个核心的反爬技巧,能让你的Selenium爬虫被检测的概率降低90%以上。
1. 随机化请求间隔
这是最基本也是最重要的一点。
真人浏览网页的时候,操作间隔是随机的,而不是固定的。如果你写的爬虫每隔0.1秒就点击一次,傻子都能看出来是机器人。
我一般会在每个操作之间加入随机延迟:
- 页面加载后等待1-3秒
- 每条数据提取之间等待0.5-1秒
- 翻页后等待2-5秒
2. 模拟人类滚动行为
很多网站会检测页面的滚动行为。如果你的页面一打开就直接定位到最底部,那肯定会被怀疑。
我们可以用JavaScript模拟人类的滚动行为:
defscroll_down(driver,scroll_times=3):for_inrange(scroll_times):# 随机滚动距离scroll_height=random.randint(300,800)driver.execute_script(f"window.scrollBy(0,{scroll_height})")time.sleep(random.uniform(0.5,1.5))3. 定期更换User-Agent
不要一直用同一个User-Agent。你可以准备一个User-Agent池,每次启动浏览器的时候随机选一个。
USER_AGENTS=["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/125.0.0.0 Safari/537.36"]# 在初始化浏览器的时候随机选择chrome_options.add_argument(f"--user-agent={random.choice(USER_AGENTS)}")4. 避免重复的操作模式
真人的操作模式是多样化的。不要每次都按照完全相同的顺序执行相同的操作。
比如,你可以随机点击一些无关的链接,或者在页面上停留随机的时间。
5. 使用代理池
如果需要频繁爬取,一定要使用代理池。不然你的IP很容易被封。
Selenium配置代理也很简单:
chrome_options.add_argument(f"--proxy-server=http://{proxy_ip}:{proxy_port}")常见问题与解决方案
我把大家在实际运行代码的时候最容易遇到的问题整理了一下,每个问题都给出了具体的解决方案。
问题1:元素定位失败,提示NoSuchElementException
这是最常见的问题。原因主要有三个:
- 页面还没加载完成就开始查找元素
- 元素定位器写错了
- 微博更新了页面结构
解决方案:
- 一定要使用显式等待,不要用time.sleep()
- 尽量使用相对定位,不要使用绝对路径
- 如果页面结构更新了,重新在F12里复制元素的选择器
问题2:被检测到是自动化工具,页面空白或者弹验证码
这是因为你的反爬参数配置得不够好。
解决方案:
- 确保添加了所有我上面提到的反爬参数
- 升级Selenium到最新版本
- 尝试使用Edge浏览器代替Chrome
- 增加随机延迟,降低爬取频率
问题3:爬取的数据不完整,只有前几条
这是因为微博热搜页面是懒加载的。当你滚动到页面底部的时候,才会加载更多的内容。
解决方案:
- 在提取数据之前,先模拟滚动页面到底部
- 等待新的内容加载完成后再提取
问题4:浏览器闪退,没有任何报错
这通常是因为浏览器驱动和浏览器版本不兼容。
解决方案:
- 升级Selenium到4.6以上版本,让Selenium Manager自动管理驱动
- 如果还是不行,手动下载对应版本的驱动
进阶优化:让你的爬虫更强大
上面的代码已经可以满足基本的爬取需求了。如果你想让你的爬虫更强大,可以做以下几个优化。
1. 实现定时爬取
我们可以用schedule库实现定时爬取,比如每小时爬取一次微博热搜。
importscheduledefjob():print(f"开始定时爬取,当前时间:{time.strftime('%Y-%m-%d %H:%M:%S')}")hot_data=crawl_weibo_hot()save_to_csv(hot_data,f"weibo_hot_{time.strftime('%Y%m%d_%H%M%S')}.csv")# 每小时执行一次schedule.every().hour.do(job)whileTrue:schedule.run_pending()time.sleep(1)2. 存储数据到数据库
如果需要长期存储大量数据,建议使用MySQL或者MongoDB。
3. 实现分布式爬取
如果需要爬取大量数据,可以结合Redis和多线程,实现分布式爬取。
4. 集成验证码识别
如果遇到验证码,可以集成第三方验证码识别服务,比如打码平台。
写在最后
Selenium不是万能的,但对于动态网页来说,它绝对是最简单最有效的解决方案。
很多人看不起Selenium,觉得它速度慢,性能差。但在实际工作中,稳定性和可维护性远比速度重要。
你花了一个星期破解了微博的API,结果过了一周就失效了。而用Selenium写的爬虫,只要页面结构没有大的变化,就能一直运行。
当然,Selenium也有它的局限性。它不适合爬取海量数据,也不适合对速度要求很高的场景。
在实际项目中,我们应该根据具体情况选择合适的工具。对于简单的静态页面,用requests就够了;对于复杂的动态页面,用Selenium;对于大规模爬取,用Scrapy+分布式架构。
最后提醒大家一句,爬虫只是一种技术手段,一定要遵守法律法规,尊重网站的robots协议,不要用于非法用途。
