爬虫可以说是pyhon的代表程序之一,学好爬虫不仅可以帮助新手巩固基础,并且爬虫本身就是一个非常实用的功能,可以帮助我们获取各种资源,学会之后可以简单高效爬取想要的内容。
1.基本流程
其实爬虫的实现过程并不容易,经过这么多年的发展各个网页都做好了相应的反爬措施,如何解开这些反爬问题是技术的难点。大部分爬虫技术开发分为以下几步:
选取爬取网址URL -> 使用抓包工具 -> 选取想要资源 -> 数据分析 -> 设计程序 -> 获取/存储资源
2.抓包工具的使用
首先,我们要明白:一个网页上的内容分为一个个的资源包。我们想要爬取的资源不会是整张网页,图片也好视频也好,都是网页中所包含的资源包。利用好抓包工具,我们可以将网页加载出来的资源包截取下来,对这些工具包操作之后就可以获得想要的资源了。
以Windows系统的Microsoft Edge浏览器为例,在网页的空白处右键->检查或者直接按住Ctrl+Shift+I就可以调出我们默认的抓包工具了:
图1
点击重新刷新就可以找到一个个资源包了。
3.选择资源
了解了抓包工具之后,接下来就是选择想要的内容了。一般而言,我们想利用爬虫获取的数据无非图片,视频,文本数据。对于不同的数据,我们在资源包里面的查找方式也不尽相同:
3.1 图片 文本数据
对于图片和文本数据而言,进行资源的查找还是十分容易的。如果从网页页面html文件中获取资源,可以利用抓包工具左上角按钮(参考图1)的功能锁定你想要资源的所在位置,如:
图2
上图是文本数据,能直接找到数据本身,我们只要获取到对应的路径就可以获取到资源。而对于图片来说,资源往往是以链接的形式呈现的,如:
图3
url对应的链接就是图片的地址,如果不确定可以复制在网页中打开验证。
3.2 视频
而对于视频而言,则相对复杂。在一些普通的网站中,可以在html文件中搜索<video>,后面对应的链接打开就是视频地址。而在一些视频网站中,往往会对视频资源进行处理,m3u8的切片、防盗链的使用等等,因为是入门,只在文章后面简单介绍。
4.数据分析
如上文所述,随着技术的开发,各种网站的反爬意识越来越强,会通过各种手段阻止爬虫的运行。那么,一场爬虫设计者与反爬设计者的斗智斗勇随之诞生。在当下及未来,爬虫爬取数据只会越来越难,我们需要一双锐利的双眼发现数据之间的关联及破解各种反爬手段。在本节中,我只会简单介绍我遇见的一些情况及解决措施,可能并不全面,选看。
4.1 一些url中键值对的使用
在一些url中,往往不会是简单的模式,像百度网址https://www.baidu.com那样简单,后面会跟一个类似字典数据结构中 something = 123456 的形式,就如:
https://dushu.baidu.com/api/pc/getCatalog?data={%22book_id%22:%224306063500%22}
这样,book_id就是一个例。那么这个东西有什么用呢?这是百度小说中一本书目录的一章链接,如果我们想要将一整片小说都爬取下来,之后的小说内容对应的url会涉及到book_id的值,如打开目录的一章,其url:
https://dushu.baidu.com/api/pc/getChapterContent?data={%22book_id%22:%224306063500%22,%22cid%22:%224306063500|1569782244%22,%22need_bookinfo%22:1}(下文类似这两个链接之间关系的成为父子链接)
不难发现对应的book_id在后续爬取内容是使用了两次,那么我们在爬取的时候就需要对book_id进行保存(c_id同理)。甚至在一些url中,它的子链接会冒出一串莫名其妙的数字,其实就类似与book_id一样需要往前面的父链接找。
4.2 防盗链的使用及其特征JavaScript的动态生成
有时候,我们会发现在页面源代码和抓包工具中复制的url会不一样,其中的一段会不一样,这是因为js的二次加载会动态生成。这时我们就要小心了,将两个地址在网页中一看,会发现动态生成后的url能正常使用,源代码的url则会返回错误的状态码。
首先,防盗链的工作原理可以简化为在客户端和服务器之间添加一个媒介,正常的request是用户 -> 防盗链 -> 服务器,因此我们在设计爬虫时需要考虑在headers中加入Referer:
headers={
"User-Agent": UserAgent().random,
"Referer": "抓包中找防盗链",
}
response=requests.get(url,headers=headers)
4.3 m3u8切片
m3u8(或m3u)即视频文件,经常出现在一些视频网站上。切片即把一个完整的视频切成一段一段的,通常最大限制十几秒,在播放时选取当前片段播放,以达到节省资源的目的。如下:
#EXTM3U 文件格式
#EXT-X-Version 版本
#EXT-X-TARGETDURATION 切片最大时长
#EXT-X-MEDIA-SEQUENCE 顺序
#EXT-X-KEY 加密方式及密钥地址
#EXTINF 时长 下面的是对应相对路径
想要获得视频文件,我们就需要通过解密获得真正路径,再通过视频编辑器将ts文件连接成真正的视频。
5.设计程序
落实到python代码,大致可以分为:
输入url -> 发送请求 -> 获取内容 -> 数据操作
5.1 发送请求
图4
请求分为两种:get和post。两种方式都是你向服务器发送访问请求的过程。
请求为get时,需要注意用户代理(User-Agent)(可以理解为是代表你是用户的一个身份),如果在短时间多次请求建议服务器很有可能会屏蔽你,因为正常用户不会这么干,可以不断更换用户代理从而爬取资源。
import requests
from fake_useragent import UserAgent
response = requests.get(url,headers={'User-Agent': UserAgent().random})
请求为post时,你在发送请求时会带上数据表明个人身份(如登录时的账号密码),变成会话模式,我们需要利用会话对象(session)。我们可以在对应的资源包里的标头里找到data,或html文件里寻找,找到后在请求时加上就可以了。
session = requests.session()
data={
"loginName":"孤狼",
"password":"123456",
}
response = session.post(url,data=data)
现在有些标头里找不到data,可以去负载里找表单数据。
5.2 获取内容
获取内容很简单,不管是哪种请求方式,都可以直接调用方法。如:
response.content()
response.text()
response.json()
5.3 数据操作
这一个就不用多说,将上一步获取的内容写入文件也好,数据库也罢,在此就不过多赘述了。
6.拓展
如果你实践了上述内容,可能会有一个疑问:经过这么多的步骤,总算爬到了资源。可是就这么点内容,我直接复制粘贴好像更快?不要急,接下来要介绍爬虫之所以能取代复制粘贴的方法。
6.1 翻页
比如说我们去豆瓣上爬取电影的数据,在分析网页的时候发现不止一面,难道我们要一面面的输入url,再一面面的抓包?这很显然就破坏了爬虫的高效性。那么该怎么办呢?我们可以把各面的网址进行一个对比:
图5 第二页
图6 第三页
对比不同页数的网址,发现了什么规律?两个网址只有最后的数字不一样,并且第二个数字是第一个的两倍。那么,我们是否可以类推,得出后面页数的网址?通过循环对url进行构造,读者可以依据格式自行构建翻页的爬虫。
6.2 多层嵌套与协程
有的时候,我们想要开发的爬虫并不是一个或者一类url就够了的。比如说我们想要把一类小说全部下载下来,我们的步骤应该是 武侠类 -> 单个小说目录 -> 章节内容。具体来说,第一步记录下类型的url,通过抓包挖掘各个小说目录url之间的关联建立目录集content_urls;第二步通过单个小说的目录挖掘各个章节的关系,建立passage_urls,最后逐一击破一一爬取。
同时,我们可以进行优化。5.1提及的发送请求其实是一个同步操作,即发送请求后整个程序都会停下来等待服务器回应。而在此之间,是干不了任何事的,整个程序是一个阻塞状态。异步协程可以很好利用这个等待时间,通过await关键字挂起等待操作,让程序在等待期间执行其他的任务。以同样是同步操作的sleep(10)为例,本来让程序睡10秒钟,异步之后就会利用这10秒完成其他任务,十秒钟一到再回来执行剩余代码。具体可见之后实例。当然,学有余力的读者还可以用多线程继续优化。
6.3 html路径分析
第一眼看html文件可能比较迷茫,但是我们抓包其实也不需要完全看懂,只需要知道相同缩进的前后尖括号划分出了一个类似作用域的东西一层层相连的作用域构成了一条路径。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>简单HTML示例</title> <style> /* 全局样式 */ body { font-family: 'Microsoft YaHei', sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } /* 头部样式 */ header { background-color: #4a6fa5; color: white; padding: 20px; border-radius: 8px; text-align: center; } /* 内容区域样式 */ .content { background-color: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } /* 按钮样式 */ button { background-color: #4a6fa5; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; margin-top: 15px; } button:hover { background-color: #3a5ca5; } /* 列表样式 */ .list-container { margin-top: 20px; padding: 20px; background-color: #f9f9f9; border-radius: 6px; } </style> </head> <body> <!-- 页面头部 --> <header> <h1>欢迎来到简单HTML示例页面</h1> <p>这是一个包含基本结构、样式和交互的HTML页面</p> </header> <!-- 主要内容区域 --> <div class="content"> <!-- 段落文本 --> <p>HTML(超文本标记语言)是构成网页的基础技术之一,它通过标签定义页面结构和内容。</p> <!-- 图片元素 --> <div style="margin: 20px 0;"> <img src="https://picsum.photos/800/400" alt="示例图片" style="width: 100%; border-radius: 6px;"> </div> <!-- 列表元素 --> <h3>HTML基本组成部分:</h3> <ul> <li><strong>DOCTYPE声明</strong>:定义HTML文档类型</li> <li><strong>html标签</strong>:包裹整个网页内容</li> <li><strong>head标签</strong>:包含元数据、样式和脚本</li> <li><strong>body标签</strong>:包含页面可见内容</li> </ul> <!-- 表单元素 --> <h3>简单交互表单:</h3> <form id="user-form"> <div style="margin-bottom: 15px;"> <label for="name">姓名:</label> <input type="text" id="name" placeholder="请输入你的名字" style="padding: 8px; border-radius: 4px; border: 1px solid #ddd; width: 100%;"> </div> <button type="button" id="greet-btn">打招呼</button> </form> <!-- 动态内容区域 --> <div id="greeting" style="margin-top: 20px; padding: 15px; background-color: #e6f2ff; border-radius: 4px; display: none;"> <h4 id="greeting-text"></h4> </div> </div> <!-- 页脚 --> <footer style="margin-top: 40px; text-align: center; color: #666; font-size: 14px;"> <p>© 2023 简单HTML示例 | 仅供学习参考</p> </footer> <!-- JavaScript 交互逻辑 --> <script> // 获取DOM元素 const greetBtn = document.getElementById('greet-btn'); const greetingDiv = document.getElementById('greeting'); const greetingText = document.getElementById('greeting-text'); const nameInput = document.getElementById('name'); // 按钮点击事件 greetBtn.addEventListener('click', function() { const name = nameInput.value.trim(); if (name) { greetingText.textContent = `你好,${name}!欢迎访问这个HTML示例页面。`; greetingDiv.style.display = 'block'; // 3秒后隐藏提示 setTimeout(function() { greetingDiv.style.display = 'none'; }, 3000); } else { alert('请输入你的名字'); } }); </script> </body> </html>
6.3.1 bs4(beautifulsoup)
7.编者留言
1.这是爬虫的基本框架,若是将整篇文章吃干抹净足以开发爬虫软件,但是往后只会被淘汰;如果不想被淘汰,不想白学,不想止步于一些简单网页的爬虫,需自行研究反反爬手段。
2.本文只有爬虫技术没有爬虫协议,如http、https、robots协议,如果不想越学越刑,不想爬虫圈传来噩耗,建议遵循相关法律条文。
3.我也资历尚浅,若是有不对或有新的反反爬手段欢迎评论区分享。若是文章看完还是不懂,推荐b站的课程:【吊打付费】目前B站最完整的Python爬虫教程,包含所有干货内容!这还没人看,我不更了!_哔哩哔哩_bilibili
虽然有些过时,但是知识点对于新手足够了。
4.注意编码问题!
8.实战
接下来的代码将展示一个在百度小说上爬取西游记小说的爬虫,包含基本步骤、协程操作、url中键值对使用,读者一定要自己进行抓包分析url数据找规律:
import aiohttp
import asyncio
import requests
import aiofiles
# https://dushu.baidu.com/api/pc/getCatalog?data={%22book_id%22:%224306063500%22}
# https://dushu.baidu.com/api/pc/getChapterContent?data={%22book_id%22:%224306063500%22,%22cid%22:%224306063500|1569782244%22,%22need_bookinfo%22:1}
async def aiodownload(book_id,c_id,title):
url = "https://dushu.baidu.com/api/pc/getChapterContent?data={%22book_id%22:%22"+book_id+"%22,%22cid%22:%22"+book_id+"|"+c_id+"%22,%22need_bookinfo%22:1}"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
dict = await response.json()
async with aiofiles.open(rf"F:\Test\{title}",mode="w",encoding="utf-8") as f:
await f.write(dict["data"]["novel"]["content"])
async def getChapter(url):
response = requests.get(url)
dict = response.json()
tasks = []
for item in dict["data"]["novel"]["items"]:
c_id = item["cid"]
title = item["title"]
tasks.append(asyncio.create_task(aiodownload(book_id,c_id,title)))
await asyncio.wait(tasks)
if __name__ == '__main__':
book_id = "4305547728"
root = "https://dushu.baidu.com/api/pc/getCatalog?data={%22book_id%22:%22"+book_id+"%22}"
asyncio.run(getChapter(root))