Python爬虫-01 初识爬虫
网络爬虫是一种自动化程序,能够高效抓取互联网上的公开数据(如商品价格、新闻资讯、社交媒体内容等),广泛应用于学术研究、市场分析、舆情监控和商业决策等领域。
- 比如学术界使用爬虫追踪社会舆情、分析群体情感,
- 电商平台通过爬虫监控竞品价格以优化定价策略,搜索引擎依赖爬虫建立网页索引等。
但爬虫存在一定技术门槛,为进一步打破数据壁垒,本教程系列拟从基础爬虫(含页面分析、基础代码编写、数据提取、数据储存、通用爬虫编写)和进阶爬虫(js逆向和app逆向)两个部分对爬虫技术进行阐述,这一章我们先来认识一下爬虫。
1 什么是爬虫
网络爬虫是使用python等工具编写的,按照编写者设定的规则,模拟浏览器发送网络请求,接收请求响应,自动抓取互联网信息的程序。从定义可以看出,如果技术足够精湛,能够通过浏览器做到的,爬虫都可以完成。
爬虫可以分为通用爬虫和自定义爬虫:
通用爬虫(如八爪鱼等)通过模拟浏览器访问网站,操作简单、兼容性强,适合大多数网站的数据采集,对初学者十分友好,但爬取速度较慢且对特殊数据的处理能力有限。
自定义爬虫则是针对特定网站开发的专用程序,无需浏览器即可直接获取数据,具有速度快、数据覆盖全的优势,但开发门槛高、周期长。用户可根据任务复杂度、开发周期和效率需求灵活选择适合的爬虫类型。当下AI工具在基础代码编写和逆向分析方面更为擅长,能够在一定程度上降低开发门槛和开发周期,即使初学者无代码基础,也能够写出令人满意的自定义爬虫。基于此,本教程两个板块都会涉及,以自定义爬虫为主,通用型爬虫为辅,协助各位完成爬虫程序开发。
| 爬虫类型 | 数据可达性 | 爬取时间 | 开发时间 |
|---|---|---|---|
| 通用爬虫 | 大多数数据 | 长 | 短 |
| 自定义爬虫 | 基本所有数据 | 短 | 长 |
2 网络通信
当我们在网页上浏览各种信息时,是否想过这些数据从何而来?为什么输入网址就能获取内容?其实,这些数据都存储在远程服务器上。访问网页的过程,就是我们的设备向服务器请求数据的过程。
以抖音为例:当你想查看某条视频的评论时,你的设备会向抖音服务器发送请求,服务器收到后就会将评论数据传回,这样你就能看到具体内容了。但服务器不像邻居的电脑那样容易定位。我们如何找到这些远程服务器呢?关键就在于URL(统一资源定位符)。URL相当于数据的"门牌地址",通过它我们能精准定位数据存储的位置,从而发起访问请求。
3 爬虫的基本流程
- 确定所需要的页面是静态页面还是动态页面
- 获取需要爬取的
url(可能是网页链接,也可能是api接口) - 通过程序向
url发送请求,获取响应 - 解析并提取响应数据
- 数据储存
针对上面的流程,咱们依次来进行演示
3.1 判断页面类型
网页主要分为静态页面和动态页面两种:静态页面的数据直接嵌入在HTML文件中,内容固定不变,而动态页面的数据则由服务器实时生成或从数据库加载。要判断一个页面类型,可以在浏览器中右键点击"查看页面源代码",然后按Ctrl+F搜索页面上显示的内容——如果能找到匹配数据,说明是静态页面;若找不到,则说明是动态加载的页面。例如:
静态页面:
Top250第一个数据是肖申克的救赎,在源代码中搜索,数据存在,所以豆瓣电影Top250这个网站是一个静态页面。
接下来我们打开一个抖音短视频,点开评论区,点击查看网页源代码
动态页面:
接下来我们随便打开一个抖音短视频,点开评论区,点击查看网页源代码!
当我们搜索页面上显示的评论(如"没有我到处惹事,哆啦A梦能拍1000多集吗")却无法在
HTML源码中找到时,即可判定这是一个动态页面。区分动态页面和静态页面的关键在于数据存储方式的不同——静态页面的数据直接嵌入HTML,而动态页面的数据通常由JavaScript实时加载。这种差异决定了我们需要采用不同的数据提取方法。
3.2 获取对应的url
我们之前提到要获取数据就需要得到相应的url。静态页面和动态页面提取url的方式相同,可以使用浏览器的开发者工具进行抓包。具体操作如下:按下F12键或右键点击选择"检查"即可打开开发者工具,在"Network"(网络)标签页中,可以查看所有网络请求信息,其中就包含我们需要的目标URL。
打开开发者工具后,我们点击键盘的
F5键,刷新页面并重新获取数据。当数据加载后,点击页面的放大镜,在搜索框中输入相应的数据进行检索,以此便能定位到数据的来源(url和具体数据信息)。
3.3 向服务器发送请求
现在我们已经获取了目标URL,接下来需要通过代码模拟浏览器向服务器发送请求。虽然编写请求代码听起来有些复杂,但我们可以借助一些现成的工具来简化这个过程。这里推荐一个实用的在线工具:spidertools.cn/#/curl2Requ… 具体操作步骤如下:
- 选中目标URL并右键点击
- 选择"复制"选项
- 在下拉菜单中选择"以
curl(bash)格式复制"(注意:目前主流工具都支持bash格式解析,请确保选择此格式) 这个工具可以帮助我们快速生成Python的requests库代码,大大简化了开发流程。接下来将复制的curl指令直接复制到网站里面,网站就会生成python代码,我们可以复制将生成的代码,将代码放入pycharm、vscode等IDE中运行。
3.4 数据解析
通过模拟浏览器发送请求的过程,我们能够直接通过代码获取到相应的数据。直接获取的数据过于冗余,为拿到所需要的数据,我们还需对原始数据进行提取。静态页面和动态页面因数据存储方式的不同,导致我们获取的原始数据有较大差异(如下图所示),因此会采取不同的方式提取这两种页面。
3.4.1 静态页面数据提取
静态页面的数据直接存储在HTML文件中。HTML本质上是一种标记文本文件,它通过特定的标签语法来定义内容结构和样式。例如,使用<h1>标题文字</h1>标签可以将文字设置为一级标题样式,类似的标签系统让HTML能够精确控制页面元素的呈现方式。这种标签的设置使相似元素有相同呈现方式,这给我们定位元素带来了便利。例如豆瓣电影top250的电影数据存在于span标签中,我们便能够通过定位span标签来定位数据。这里我们通过xpath来定位电影标题。(ps: xpath是一门在XML文档中查找信息的语言,在数据提取部分将着重提及,有兴趣的朋友可先自行百度了解)
3.4.2 动态页面数据提取
要提取动态页面的数据,需要先分析其数据结构。以抖音评论为例,在开发者工具中可以看到类似这样的结构:
{
"comments": [
{
"text": "《没有我到处惹事,哆啦A梦能拍1000多集吗》"
}
]
}
这表示评论数据是以JSON格式存储的,最外层是包含comments键的字典,而每条评论又是一个包含text键的子字典。通过解析这个嵌套结构,就能准确提取出所需的评论内容。
再点击一下上面的预览,我们发现页面中有一个
comments的字段。如果我们点开comments下面的数据,会发现每一条评论都会有具体的数据,包括ip位置,点赞数量,用户名之类的,具体字段提取需结合具体任务。
由于网络传输返回的是
JSON格式的字符串数据,直接处理字符串提取数据较为不便。我们可以通过调用.json()方法,将响应数据转换为Python字典格式,这样就可以方便地通过键值对来访问数据了。这里我提取了用户名、ip属地、评论、点赞数和回复数五个数据:具体实现如下:
3.5 数据储存
现阶段咱们的数据量不是很大,直接用csv来储存即可。csv文件是一个普通的文本文件,但是里面的每一行数据都是用,分开,csv文件可以直接用excel来打开,相对来说比较方便。
import csv # 导入csv的包,咱们操作都需要用这个包来完成
ans = []
title = ["用户名", "ip属地", "评论", "点赞数", "回复数"] # 字段
for item in response["comments"]:
goods = item["digg_count"] # 点赞数
ip = item["ip_label"] # ip地址
text = item["text"] # 评论
reply = item["reply_comment_total"] # 回复数量
name = item["user"]["nickname"] # 用户名称
ans.append([name, ip, text, goods, reply]) # 用列表保存数据
with open("reply.csv", "a+", encoding="utf-8-sig", newline="") as f:
f = csv.writer(f)
f.writerow(title) # 写入字段信息
f.writerows(ans) # 写入数据
至此,咱们爬虫的基本流程结束,这一节主要介绍了什么是爬虫,爬虫的基本流程。这一次的教程更多的是和大家一起走一次爬虫的基本流程,那么在之后的教程中将会进一步细化每一个过程。本章结束后各位可以去某联招聘网站逛一逛,尝试抓取招聘数据,熟悉流程。
咱们走完流程了,你可能在想,这么一通复杂的操作,咱们就拿下10条数据,还没直接复制快,所以,在最后写一段简单的爬评论代码,通过上面的解析网站提取ttwid和aweme_id,填写至下面代码即可运行,代码仅供学习参考。
import requests
import csv
class DouYin:
def __init__(self, dyid):
self.id = dyid
self.headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
}
self.url = "https://www.douyin.com/aweme/v1/web/comment/list/"
with open("reply.csv", "w+", encoding="utf-8-sig", newline="") as f:
f = csv.writer(f)
f.writerow(["用户名", "ip属地", "评论", "点赞数", "回复数"])
def request_comments(self, start):
'''发送一次请求,获取10条评论数据,a_bogus不做验证,可固定'''
params = {
"aid": "6383",
"aweme_id": self.id,
"cursor": str(start),
"count": "10",
"a_bogus": "Oj45gw7wdNWcFd/SmKBfSWCUhznMNT8yKqT2bqdUexxiGHUbcSPUZnSTroKU/d/7ubBzhH1HrfGAbjdcQ4thZK9kwmhkSQsfOz/I9g8o8qqhTlG/DrRuChUzKw0e0mkNeA9ji1mRUs0rIjnRnq5uAppae5zoQ5EgSHBVp2SyJDSW3syTI9dbC-iAnwE=",
}
try:
response = requests.get(
self.url, headers=self.headers, cookies=cookies, params=params
).json()
except Exception as e:
print("[ERROR]: ", e)
response = {}
return response.get("comments")
def request_comments_pages(self, counts):
'''main函数,用于获取指定数量评论'''
for count in range(0, counts, 10):
result = self.request_comments(count)
if result is None:
break
ans = []
for item in result:
goods = item.get("digg_count") # 点赞数
ip = item.get("ip_label") # ip地址
text = item.get("text") # 评论
reply = item.get("reply_comment_total") # 回复数量
name = item.get("user", {}).get("nickname") # 用户名称
ans.append([name, ip, text, goods, reply])
self.save_data(ans)
print(f"[SUCCESS]: {count + len(ans)}条数据保存成功....")
def save_data(self, data):
'''用于保存数据'''
with open("reply.csv", "a+", encoding="utf-8-sig", newline="") as f:
f = csv.writer(f)
f.writerows(data)
if __name__ == "__main__":
ttwid = # cookie信息,需要ttwid字段,只需要修改一次
aweme_id = # 视频的id,需要aweme_id字段,不同视频aweme_id不同
counts = 1000 # 需要的评论数量
cookies = {"ttwid": ttwid}
dy = DouYin(aweme_id)
dy.request_comments_pages(counts)
关于aweme_id和cookie的获取,不同视频的aweme_id是不同的,换一个视频需要更换aweme_id,但是cookie是不需要的修改的,填了一次之后就不用再填了。