python爬虫重定向 爬网页 遇到重定向怎么处理

好像是网址转移了需要重定向嘚问题,请问我要怎么改代码获得我想要的信息

主要针对以下四种反爬技术:Useragent过濾;模糊的Javascript重定向;验证码;请求头一致性检查
高级网络爬虫技术:绕过 “403 Forbidden”,验证码等

爬虫的完整代码可以在 github 上对应的仓库里找到

我從不把爬取网页当做是我的一个爱好或者其他什么东西,但是我确实用网络爬虫做过很多事情因为我所处理的许多工作都要求我得到无法以其他方式获得的数据。我需要为 Intoli 做关于游戏数据的静态分析所以我爬取了Google应用商店的数据来寻找最新被下载的APK。Pointy Ball插件需要聚合来自鈈同网站的梦幻足球(游戏)的预测数据最简单的方式就是写一个爬虫。在我在考虑这个问题的之前我大概已经写了大约 40~50 个爬虫了。我不呔记得当时我对我家人撒谎说我已经抓取了多少 TB 的数据….但是我确实很接近那个数字了。

我尝试使用 xray/cheerio、nokogiri 和一些其他的工具但我总是会囙到我个人的最爱 Scrapy。在我看来Scrapy是一个出色的软件。我对这款软件毫无保留的赞美是有原因的它的用法非常符合直觉,学习曲线也很平緩

你可以阅读Scrapy的教程,在几分钟内就可以让你的第一个爬虫运行起来然后,当你需要做一些更复杂的事情的时候你就会发现,有一個内置的、有良好文档说明的方式来做到这一点这个框架有大量的内置功能,但是它的结构使得在你用到这些功能之前不会妨碍到你。当你最终确实需要某些默认不存在的功能的时候比如说,因为访问了太多的 URL 链接以至于无法存储到内存中需要一个用于去重的 bloom filter(布隆過滤器),那么通常来说这就和继承其中的组件然后做一点小改动一样简单。一切都感觉如此简单而且scrapy是我书中一个关于良好软件设计嘚例子。

我很久以前就想写一个高级爬虫教程了这给我一个机会来展示scrapy的可扩展性,同时解决实践中出现的现实问题尽管我很想做这件事,但是我还是无法摆脱这样一个事实:因为发布一些可能导致他人服务器由于大量的机器人流量受到损害的文章就像是一个十足的壞蛋。

只要遵循几个基本的规则我就可以在爬取那些有反爬虫策略的网站的时候安心地睡个好觉。换句话说,我让我的请求频率和手动浏覽的访问频率相当并且我不会对数据做任何令人反感的事情。这样就使得运行爬虫收集数据基本上和以其他主要的手动收集数据的方法無法区分但即使我遵守了这些规则,我仍感觉为人们实际想要爬取的网站写一个教程有很大的难度

直到我遇到一个叫做Zipru的BT下载网站,這件事情仍然只是我脑海里一个模糊的想法这个网站有多个机制需要高级爬取技术来绕过,但是它的 robots.txt 文件却允许爬虫爬取此外,其实峩们不必去爬取它因为它有开放的API,同样可以得到全部数据如果你对于获得torrent的数据感兴趣,那就只需要使用这个API这很方便。

在本文嘚剩余部分我将带领你写一个爬虫,处理验证码和解决我们在Zipru网站遇到的各种不同的挑战样例代码无法被正常运行因为 Zipru 不是一个真实存在的网站,但是爬虫所使用的技术会被广泛应用于现实世界中的爬取中因此这个代码在另一个意义上来说又是完整的。我们将假设你巳经对 python爬虫重定向 有了基本的了解但是我仍会尽力让那些对于 Scrapy 一无所知的人看懂这篇文章。如果你觉得进度太快那么花几分钟的时间閱读一下Scrapy官网教程吧。

你运行的终端将被配置为使用本地的virtualenv如果你打开另一个终端,那么你就需要再次运行. ~/scrapers/zipru/env/bin/active 命令 (否则你有可能得到命令戓者模块无法找到的错误消息)

现在你可以通过运行下面的命令来创建一个新的项目框架:
这样就会创建下面的目录结构。
大多数默认情况丅产生的这些文件实际上不会被用到它们只是建议以一种合理的方式来构建我们的代码。从现在开始你应该把 ~/scrapers/zipru/zipru_scraper 当做这个项目的根目录。这里是任何scrapy命令运行的目录同时也是所有相对路径的根。

添加一个基本的爬虫功能

现在我们需要添加一个Spieder类来让我们的scrapy真正地做一些倳情Spider类是scrapy爬虫用来解析文本,爬取新的url链接或是提取数据的一个类我们非常依赖于默认Spider类的实现,以最大限度地减少我们必须要编写嘚代码量这里要做的事情看起来有点自动化,但假如你看过文档事情会变得更加简单。

你可以在上面的网页中看到许多指向其他页面嘚连接我们想让我们的爬虫跟踪这些链接,并且解析他们的内容为了完成这个任务,我们首先需要识别出这些链接并且弄清楚他们指姠的位置

在这个阶段,DOM检查器将起到很大的助力如果你右击其中的一个页面链接,在DOM检查器里面查看它然后你就会看到指向其他页媔的链接看起来像是这样的:
接下来我们需要为这些链接构造一个选择器表达式。有几种类型似乎用css或者xpath选择器进行搜索更适合所以我通瑺倾向于灵活地混合使用这几种选择器。我强烈推荐学习xpath但是不幸的是,它有点超出了本教程的范围我个人认为xpath对于网络爬虫,web UI 测试甚至一般的web开发来说都是不可或缺的。我接下来仍然会使用css选择器因为它对于大多数人来说可能比较熟悉。

要选择这些页面链接我們可以把 a[title ~= page] 作为一个 css 选择器,来查找标题中有 “page” 字符的 <a>标签如果你在 DOM 检查器中按 ctrl-f,那么你就会发现你也可以使用这个css表达式作为一条查找语句(也可以使用xpath)这样我们就可以循环查看所有的匹配项了。这是一个很棒的方法可以用来检查一个表达式是否有效,并且表达式足夠明确不会在不小心中匹配到其他的标签我们的页面链接选择器同时满足了这两个条件。

为了讲解我们的爬虫是怎样发现其他页面的峩们在 ZipruSpider 类中添加一个 parse(response) 方法,就像下面这样:
当我们开始爬取的时候我们添加到 start_urls 中的链接将会被自动获取到,响应内容会被传递到 parse(response) 方法中の后我们的代码就会找到所有指向其他页面的链接,并且产生新的请求对象这些请求对象将使用同一个 parse(response) 作为回调函数。这些请求将被转囮成响应对象只要 url 仍然产生,响应就会持续地返回到

我们的爬虫已经可以找到了页面中列出的所有不同的页面并且对它们发出了请求,但我们仍然需要提取一些对爬虫来说有用的数据torrent 列表位于 <table> 标签之内,并且有属性 class="list2at" 每个单独的 torrent 都位于带有属性 class="lista2" 的 <tr> 标签,其中的每一行嘟包含 8 个 <td>标签分别与 “类别”,“文件”“添加时间”,“文件大小”“保种的人”,“下载文件的人”“文件描述”,和“上傳者”相对应在代码中查看其它的细节可能是最简单的方法,下面是我们修改后的 parse(response) 方法:
我们的 parse(response) 方法现在能够返回字典类型的数据并且根据它们的类型自动区分请求。每个字典都会被解释为一项并且作为爬虫数据输出的一部分。

如果我们只是爬取大多数常见的网站那峩们已经完成了。我们只需要使用下面的命令来运行:

几分钟之后我们本应该得到一个 [JSON Lines] 格式 torrents.jl 文件里面有我们所有的torrent 数据。取而代之的是我們得到下面的错误信息(和一大堆其他的东西):
我好气啊!我们现在必须变得更聪明来获得我们完全可以从公共API得到的数据因为上面的代码詠远都无法爬取到那些数据。

我们的第一个请求返回了一个 403 响应所以这个url被爬虫忽略掉了,然后一切都关闭了因为我们只给爬虫提供叻一个 url 链接。同样的请求在网页浏览器里运行正常即使是在没有会话(session)历史的隐匿模式也可以,所以这一定是由于两者请求头信息的差异慥成的我们可以使用 tcpdump 来比较这两个请求的头信息,但其实有个常见错误所以我们应该首先检查: user agent 。

你可能注意到了默认的scrapy设置中有一些令爬虫蒙羞的事。关于这个问题的观点众说纷纭但是我个人认为假如你想让爬虫表现的像是一个人在使用普通的网页浏览器,那么你僦应该把你的爬虫设置地像普通的网络浏览器那样所以让我们一起添加下面的设置来降低一下爬虫响应速度:
通过 AutoThrottle 扩展 ,上面的设置会创建一个稍微真实一点的浏览模式我们的爬虫在默认情况下会遵守 robots.txt,所以现在我们的行为非常检点

这是一个巨大的进步!我们获得了两個 200 状态码和一个 302状态码,下载中间件知道如何处理 302 状态码不幸的是,这个 302 将我们的请求重定向到了一个看起来不太吉利的页面 threat_defense.php不出所料,爬虫没有发现任何有用的东西然后爬虫就停止运行了。

注: 假如网站检测到你的爬虫那么网站就会把你的请求重定向到 threat_defense.php 页面,使你嘚爬虫失效用来防止频繁的爬虫请求影响了网站正常用户的使用。

在我们深入研究我们目前所面临的更复杂的问题之前先了解一下请求和响应在爬虫中是怎样被处理的,将会很有帮助当我们创建了我们基本的爬虫,我们生成了一个 scrapy.Request 对象然后这些请求会以某种方法转囮为与服务器的响应相对应的 scrapy.Response对象。这里的 “某种方法” 很大一部分是来自于下载中间件

方法。你大概可以从他们的名字中猜到他们是莋什么的实际上这里有一大堆的默认开启的中间件。下面是标准的中间件配置(你当然可以禁用、添加或是重新设置这些选项):
当一个请求箌达服务器时他们会通过每个这些中间件的 process_request(request, spider) 方法。 这是按照数字顺序发生的RobotsTxtMiddleware 中间件首先产生请求,并且 HttpCacheMiddleware 中间件最后产生请求一旦接收到一个响应,它就会通过任何已启用的中间件的 process_response(request,response,spider) 方法来返回响应这次是以相反的顺序发生的,所以数字越高越先发送到服务器数字樾低越先被爬虫获取到。

一个特别简单的中间件是 CookiesMiddleware它简单地检查响应中请求头的 Set-Cookie,并且保存 cookie 然后当响应返回的时候,他们会适当地设置 Cookie 请求头标记这样这些标记就会被包含在发出的请求中了。这个由于时间太久的原因要比我们说的要稍微复杂些但你会明白的。

另一個相对基本的就是 RedirectMiddleware 中间件它是用来处理 3XX 重定向的。它让一切不是 3XX 状态码的响应都能够成功的通过但假如响应中还有重定向发生会怎样? 唯一能够弄清楚服务器如何响应重定向URL的方法就是创建一个新的请求,而且这个中间件就是这么做的当 process_response(request, response, spider) 方法返回一个请求对象而不是响應对象的时候,那么当前响应就会被丢弃一切都会从新的请求开始。这就是 RedirectMiddleware 中间件怎样处理重定向的这个功能我们稍后会用到。

如果伱对于有那么多的中间件默认是开启的感到惊讶的话那么你可能有兴趣看看 体系架构概览。实际上同时还有很多其他的事情在进行但昰,再说一次scrapy的最大优点之一就是你不需要知道它的大部分原理。你甚至不需要知道下载中间件的存在却能写一个实用的爬虫,你不必知道其他部分就可以写一个实用的下载中间件

回到我们的爬虫上来,我们发现我们被重定向到某个 threat_defense.php?defense=1&... URL上而不是我们要找的页面。当我們在浏览器里面访问这个页面的时候我们看到下面的东西停留了几秒:

看看第一个页面的源代码就会发现,有一些 javascript 代码负责构造一个特殊嘚重定向URL并且构造浏览器的cookies。如果我们想要完成这个任务那我们就必须同时解决上面这两个问题。

接下来当然我们也需要解决验证碼并提交答案。如果我们碰巧弄错了那么我们有时会被重定向到另一个验证码页面,或者我们会在类似于下面的页面上结束访问:

在上面嘚页面中我们需要点击 “Click here” 链接来开始整个重定向的循环,小菜一碟对吧?

我们所有的问题都源于最开始的 302 重定向,因此处理它们的方法自然而然应该是做一个自定义的 重定向中间件我们想让我们的中间件在所有情况下都像是正常重定向中间件一样,除非有一个 302 状态码並且请求被重定向到 threat_defense.php 页面当它遇到特殊的 302 状态码时,我们希望它能够绕过所有的防御机制把访问cookie添加到 session 会话中,最后重新请求原来的頁面如果我们能够做到这一点,那么我们的Spider类就不必知道这些事情因为请求会全部成功。

response, spider) 函数就会调用这个函数我们只是把对于普通的重定向的处理推迟到父类进行处理,但是对于特殊的威胁防御重定向的处理是不一样的我们到目前为止还没有实现 bypass_threat_defense(url) 方法,但是我们鈳以知道它应该返回访问cookies并把它附加到原来的请求中,然后原来的请求将被重新处理

这会禁用默认的重定向中间件,并且把我们的中間件添加在中间件堆栈中和默认重定向中间件相同的位置我们必须安装一些额外的包,虽然我们现在没有用到但是稍后我们会导入它們:
请注意,这三个包都有 pip 无法处理的外部依赖如果你运行出错,那么你可能需要访问 dryscrape Pillow, 和 pytesseract 的安装教程遵循平台的具体说明来解决。

峩们的中间件现在应该能够替代原来的标准重定向中间件现在我们只需要实现 bypass_thread_defense(url) 方法。我们可以解析 javascript 代码来得到我们需要的变量然后用 python爬虫重定向 重建逻辑,但这看起来很不牢靠而且需要大量的工作。让我们采用更简单的方法尽管可能还是比较笨重,使用无头的 webkit 实例有几个不同选择,但我个人比较喜欢

首先让我们在中间件构造函数中初始化一个 dryscrape 会话。
你可以把这个会话对象当做是一个单独的浏览器标签页它可以完成一切浏览器通常可以做的事情(例如:获取外部资源,执行脚本)我们可以在新的标签页中打开新的 URL 链接,点击一些东覀或者在输入框中输入内容,或是做其他的各种事情Scrapy 支持并发请求和多项处理,但是响应的处理是单线程的这意味着我们可以使用這个单独的 dryscrapy 会话,而不必担心线程安全

现在让我们实现绕过威胁防御机制的基本逻辑。
这样就处理了我们在浏览器中遇到的所有不同的凊况并且完全符合人类在每种情况中的行为。在任何给定情况下采取的措施都取决于当前页面的情况所以这种方法可以稍微优雅一点哋处理各种不同的情况。

最后一个难题是如果如何解决验证码网上提供了 验证码识别 服务,你可以在必要时使用它的API但是这次的这些驗证码非常简单,我们只用 OCR 就可以解决它使用 pytessertact 的 OCR 功能,最后我们可以添加 solve_captcha(img) 函数这样就完善了 bypass_threat_defense() 函数。
你可能注意到如果验证码因为某些原因识别失败的话它就会委托给 back to the bypass_threat_defense() 函数。这样就给了我们多次识别验证码的机会但重点是,我们会在得到正确结果之前一直在验证码识別过程中循环

这应该足够让我们的爬虫工作,但是它有可能陷入死循环中
至少看起来我们的中间件已经成功地解决了验证码,然后补發了请求问题在于,新的请求再次触发了威胁防御机制我第一个想法是我可能在怎样解析或是添加cookie上面有错误,但是我检查了三次玳码是正确的。这是另外一种情况 “唯一可能不同的事情就是请求头”

很明显,scrapy 和 dryscrape 的请求头都绕过了最初的触发 403 响应的过滤器因为我們现在不会得到任何 403 的响应。这肯定是因为它们的请求头信息不一致导致的我的猜测是其中一个加密的访问cookies包含了整个请求头信息的散列值,如果这个散列不匹配就会触发威胁防御机制。这样的目的可能是防止有人把浏览器的cookie复制到爬虫中去但是它只是增加了你需要解决的问题而已。

注意我们已经把 User-Agent 头信息修改成了我们之前定义的 USER_AGENT 中去.这个工作是由 user agent 中间件自动添加进去的但是把所有的这些配置放到┅个地方可以使得 dryscrape 更容易复制请求头信息。我们可以通过修改 ThreatDefenceRedirectMiddleware 初始化函数像下面这样:
现在当我们可以通过命令 scrapy crawl zipru -o torrents.jl 再次运行爬虫。我们可鉯看到源源不断的爬取的内容并且我们的 torrents.jl 文件记录把爬取的内容全部记录了下来。我们已经成功地绕过了所有的威胁防御机制

我们已經成功地写了一个能够解决四种截然不同的威胁防御机制的爬虫,这四种防御机制分别是:

我们的目标网站 Zipru 可能是虚构的但是这些机制都昰你会在真实网站上遇到的真实的反爬虫技术。希望我们使用的方法对你自己爬虫中遇到的挑战有帮助

最后,如果你跟我一样都喜欢python爬蟲重定向想成为一名优秀的程序员,也在学习python爬虫重定向的道路上奔跑欢迎你加入python爬虫重定向学习群: 群内每天都会分享最新业内资料,分享python爬虫重定向免费课程共同交流学习,让学习变(编)成(程)一种习惯!

   如果没有登录的话招标详情一些关键信息会被隐藏,像这样:

   而登录后这些信息都会展示出来

   经过分析,本次爬虫需要向三个页面请求数据第一个是登录页面,第②个是请求每一页中的数据第三个根据返回的数据找到每个公告的详情网址,向这个网址请求详细的数据这么说有点抽象,直接来看烸一步做了什么

   打开开发者工具,选择network一栏按照如图所示的顺序操作。

   首先点击登录会弹出一个登录框,但是在开发者工具并没有看到网址发生变化猜测只是一个js操作,起了一个线程网址没有发生变化。但是这不影响我们操作我们不输入任何登录信息,直接点擊“立即登录”一个网址赫然在列:

    可以看到请求的网址是: , 请求携带三个数据以供服务器校验由此构造requests请求:

 # 返回的是一个json的二進制数据,先将其转成utf-8编码格式
 # 用登录时获得的session继续请求每个公告的主页这样就可以保持登录状态
 # 由于公告内容的格式不一样,两种格式需要分别处理
 # 有正规的表格的是以2开头的时间
 # 没有正规表格就把全部数字都抓取下来
 home() # 获得每个公告的主页并分别处理它们

我要回帖

更多关于 python爬虫重定向 的文章

 

随机推荐