原标题:Python 带你薅羊毛:手把手教你揪出特价机票信息

简单地说,我们的目标是编写一个网络爬虫,帮你自动搜索飞往特定目的地,时间在一个弹性范围(在首选日期前后最多3天)内的航班价格。它会把搜索结果保存在一个 Excel 表格中,并把精炼过的统计信息通过电子邮件发送给你。显然,我们要找的就是对应时段中最优惠的机票。

译注:如果你在阅读本文的时候遇到困难,建议试着看看我们的往期文章,特别是介绍用 Python 编写网络爬虫的那些。

在现实生活中,爬虫的用途完全取决于你。我曾经用它安排过两次假期旅行,还搜索过一些回我老家的短途航班信息。

如果你想要弄得专业一点,你可以把这个程序放在服务器上(一个简单的树莓派就够了),让它每天运行上一两次。程序将会把统计结果发到你的邮箱里,我也建议你把生成的 Excel 表格保存到网盘中(比如 Dropbox),这样你就能方便地在任何地方查阅数据。

它会搜索一个“弹性”的日期范围,以便查找在你首选日期前后 3 天内的航班信息。尽管这个脚本一次只能查询一对目的地(出发-到达),但你很容易就能对它进行调整,以在每个循环内运行多次。你甚至可能发现一些标注错误的超低票价……那简直是"棒极了"(小编注:不提倡钻这种空子)

我目前还没发现这类出错的机票,不过我想我会成功的!

译注:本文的完整源代码可以在作者的 Github 页面上下载到,有需要的读者可以对照代码阅读本文,以方便理解。

又一个爬虫?

当我第一次开始做网络抓取工作的时候,我对这个方面……并不是太感兴趣。没错,这是真心话。我那时候更希望搞些预测性的建模,或是金融分析,甚至一些语义情绪分析之类的“高级”项目。但事实证明,想方设法编写出第一个网络爬虫的过程,还是相当有趣的。随着我学习的不断深入,我逐渐意识到,网络抓取正是驱动互联网本身能够运行的主要推手。

你可能觉得我是章口就莱,但如果你知道,Google 最开始就是建立在 Larry Page 用 Java 和 Python 写的一个爬虫上的呢?这个爬虫差不多是字面意义上地把整个互联网都抓了下来(即使现在也是如此),以便当你在搜索框里输入关键字的时候,程序能够给你提供最佳的答案。网络爬虫在互联网上的实际应用几乎是无穷无尽的。退一万步说,就算你从事的是数据科学中的其他领域,你仍然需要一些网络抓取技能来帮你从互联网上获取数据。

“你喜欢旅行吗?”

这个简单而无害的问题,常常能得到别人肯定的答复,偶尔还会收获一两个之前的冒险故事。我想大部分人应该都同意,旅行是体验新文化,拓展自己眼界的好办法。但是,如果问题变成“你喜欢订机票的过程吗?”,我想大家的热情一定会打上许多折扣吧…

这就是 Python 大显身手的时候啦。

第一个挑战是,该选择从哪个平台获取信息。这并不是个容易的决定。最后,我选择了 Kayak。在这个过程中,我也考虑过 Momondo、Skyscanner、Expedia 以及一些其他的网站,不过对初学者来说,这些网站的人机验证实在是……比较无情。在选过几次“哪个是红绿灯,哪个是人行道和自行车”,点过几次“我不是机器人”之后,我觉得还是 Kayak 比较友好一点——虽然如果你在短时间内同时读取太多页面的话,它也会给你弹一些安全检查什么的。

我目前让脚本大约每隔 4 到 6 个小时就抓一次网页,虽然偶尔会出现一些小问题,但总体上还是比较 OK 的。如果你发现脚本开始一直碰到验证码,你可以试着手动提交验证,然后重启脚本,也可以等上几个小时再让爬虫访问这个网站,那时候验证码应该就消失了。

你也可以试着把这些代码用在其他平台上,欢迎大家在下面留言分享你的成果!

在真正开始之前,我要强调很重要的一点:如果你还不熟悉网络抓取,或者如果你不知道为什么某些网站费尽全力要阻止爬虫,那么在你写下第一行爬虫代码之前,请先 Google 一下“网络爬虫礼仪”。如果你像疯子一样毫无节制地扒别人的网站,可能很快就会碰一鼻子灰。

译注:也请大家严格遵守我国互联网和计算机系统方面的各种法律法规及相关规定,不要滥用爬虫技术。

系紧安全带

准备,加速!

在你导入所需的库,并打开一个 Chrome 页面之后,我们需要定义一些之后会在循环中调用的函数。主要的程序结构应该差不多类似这样:

一个函数负责启动爬虫,指出我们需要查找的城市和日期

这个函数获取到最初的搜索结果,按照“最优”方式排序航班列表,然后点击“载入更多”

另外一个函数爬取整个页面,返回一个 dataframe 数据表对象

重复上面的步骤 2 和 3,用“最便宜”和“最快速”的方式排序列表

简单地对价格进行统计(最低价、平均价),然后形成简要汇总表,发送到指定邮箱,并把对应的 dataframe 保存成 Excel 表格文件,放在指定目录中

每隔 X 小时就重复一遍上面的每一步

通常情况下,每一个 Selenium 项目都是从一个网页驱动框架(webdriver)开始的。我现在用的是 Chromedriver,它使用的是 Chrome 内核。当然,你也可以选择其他的选项,比如无头浏览器 PhantomJS,或者干脆是火狐,都很不错。下载完,往文件夹里一丢就完事了。

请各位大佬读者注意,我写这篇文章并不是为了展示什么新技术。没错,已经有更先进的方法来寻找更便宜的机票,然而我只希望我的文章能给读者带来一些简单而实用的东西。

from time import sleep, strftimefrom random import randintimport pandas as pdfrom selenium import webdriverfrom selenium.webdriver.common.keys import Keysimport smtplibfrom email.mime.multipart import MIMEMultipart

上面这些就是我们的脚本所需的代码库啦。我将用 randint() 让爬虫在每次搜索之间暂停上随机的几秒钟,这是基本上每个爬虫都会有的功能。

driver = webdriver.Chrome(executable_path=chromedriver_path)sleep(2)

开头这几行将会打开一个空白的 Chrome 页面。当你运行它的时候,你将会看到一个空白的 Chrome 浏览器窗口出现了,我们接下来就将让爬虫在这个窗口里工作。

那么,先让我们在另外一个窗口中手动打开 http://kayak.com 检查一下吧。选择你的出发和到达城市,以及出发日期。在选择日期的时候,记得选上“± 3 天”的选项。我写代码的时候是按这个选项来调试的,所以如果只想搜索某个指定日期的话,需要对代码进行一些调整。

我会在之后说明需要调整的地方,不过如果你在尝试的时候遇到问题,欢迎在下面留言哈。

接下来,我们按下搜索按钮,把地址栏里的链接地址复制下来。这个地址长得应该类似下面代码中的那个字符串。我把这个字符串赋值给 kayak 变量,并用 webdriver 的 get 方法来访问这个地址。

这时你的搜索结果页面应该出现了。

如果我在几分钟内连续执行这个命令两三次,网站就会弹出一个验证码,阻止后续的访问。 这种情况下,你可以直接手动完成验证,并继续测试你需要搜索的内容,直到下一个验证码跳出来为止。

就我个人的测试而言,头一次的搜索总是不会有问题的,所以如果你还没有跳过验证码的能力,你可以试着先手动完成验证,然后再让爬虫以较低的频率执行搜索操作。——毕竟你完全没必要每隔10分钟就搜索一次价格,对吧?

XPath:一个萝卜一个坑

目前,我们打开了一个窗口,读取了一个网页,为了能确切地获取到价格和其他信息,我们需要用到 XPath 或者 CSS 的选择器。今天的例子中,我选择用 XPath 来定位页面上的元素,因为我觉得这个例子里并不是太需要用到 CSS--当然,如果你能做到混合使用 CSS 来进行定位,那当然更完美。用 XPath 来在页面中进行跳转有的时候还是容易把人搞晕,即使你用了网上那些文章中的技巧,比如在“检查元素”中直接右键“复制 XPath”等方式来获取对应网页元素的 XPath 信息,也不见得就是最佳的办法--有的时候,这样获取的链接太特殊了,很快就不能再用了。

译注:这里个人推荐一下我个人之前买过的《Python 爬虫开发从入门到实战》(谢乾坤 著),里面比较详细地介绍了 XPath 语法,以及如何构造 XPath 的知识,当然 Selenium 的模拟登录和处理验证码等黑科技的介绍也少不了。建议学有余力的同学可以看一看。cheap_results = '//a[@data-code = "price"]'driver.find_element_by_xpath(cheap_results).click()

那么,让我们用 Python 来选中最便宜的搜索结果。上面的代码中,那个字符串就是 XPath 选择器。你可以在网页中任意元素上点击右键,选择 检查,当开发者工具弹出时,你就可以在窗口中看到你选中的元素的代码了。

为了说明一下我前面提到过的,直接在开发者工具中复制 XPath 可能存在的问题,大家可以对比一下这两个 XPath 代码:

这是在开发者工具中,右键点击并选择 复制XPath 命令后,你得到的 XPath 字符串:

'//*[@id="wtKI-price_aTab"]/div[1]/div/div/div[1]/div/span/span'

这是我实际使用的定位“最便宜”结果的 XPath 语句:

cheap_results = '//a[@data-code = "price"]'

看出问题了吗?

很明显,后面这种写法更简明易懂。它在网页上搜索,并定位一个 data-code 属性等于 "price" 的元素。

而前面这种写法,它先定位一个 ID 是 wtKI-price_aTab 的元素,然后找它的第一个子 div然后往下找 4 层 div 以及 2 层 span …… 怎么说呢,它这次应该是会成功的吧,但一旦网页层次有变化,你的代码就废了。

还是回到上面这个例子,这个 ID 是 wtKI-price_aTab 的元素,只要你刷新一下页面,它的 ID 就变了——事实上,这个 wtKI 是自动生成的字符串,它在每次搜索的时候都会不一样。也就是说,只要一刷新页面,你这段代码就没法正常工作了。

所以,我的忠告是:花点时间认真了解网页结构特征,熟悉 XPath,肯定不亏。

不过,在没那么“复杂”的网站上,直接用复制 XPath 也是可以的完成任务的。具体问题具体分析吧!

在完成了上面的这些步骤之后,搜索结果应该已经显示出来了。那么,如果我们要把所有搜索结果的字符串都读取出来,保存在一个列表对象里面,该怎么做呢?小菜一碟。观察这个页面,我们能看出,每一个搜索结果都属于 resultWrapper 类下的一个对象。那么,我们只需要用 xpath 把所有包含这个类的元素都抓下来,再弄个循环把它们塞进列表里就完事了。如果你能理解这个部分,那接下来的绝大部分代码应该都难不住你啦。

基本上,它的工作方式就是指向你想要的某个对象(比如这里的 resultWrapper),用某种方式(XPath 选择器)把文字都抓下来,然后把内容都放在某个方便读取的对象(先是 flight_containers,接着是 flights_list )中,就搞定咯。

这段的代码类似这样:

xp_results_table = '//*[@class = "resultWrapper"]'flight_containers = driver.find_elements_by_xpath(xp_results_table)flights_list = [flight.text for flight in flight_containers]# 列出前 3 个结果flights_list[0:3]

运行结果如下:

我把前三行显示出来,这样我们就能比较直观地看出程序有没有正确地获取到我们需要的信息。不过,为了方便处理多页数据,我打算单独爬取每个页面上的每个元素,最后再整合进数据表中。

全速起飞!

首先,也是最容易的函数,就是实现「加载更多」功能。我们的目标是,在一页里尽可能多地获取航班信息,同时又不触发验证码检查。所以,我的做法是,在一页内容载入进来之后,点一下(就一下!)页面上的「加载更多结果」按钮。

这基本上和我们上面讲过的代码没啥区别,只多了一个 try 语句——我加上这行是因为有的时候这个按钮会没能正确加载,而我不希望程序在这种情况下就此崩溃。

要开启这个功能,你只需要在 start_kayak 函数中把 # load_more() 前面的注释去掉就行啦。

那么,在拉拉杂杂地说了这么多之后(有的时候我真的容易跑题),我们终于到了实际抓取页面内容的函数啦!

我已经把页面上大部分需要处理的元素都丢给 page_scrape 函数来处理了。有的时候,处理结果的列表中会混杂插入第一站和第二站的经停信息,我用一个简单的方法把它们分开,存在 section_a_list 和 section_b_list 两个变量中。这个函数还返回一个数据表对象 flights_df 以便我们可以把各种不同排序的结果分门别类,并最后整合在一起。

我试着让变量名看起来比较清晰易懂一些。请记住,带有 A 的变量与行程第一段相关,而 B 与第二段相关。让我们看看下一个函数吧。

什么,还有其他函数?

是的。目前我们已经载入了一个页面,构建了一个读取更多内容的函数,以及一个爬取并处理内容的函数。其实,我大可以在这里就把文章结束掉,你还是可以用这段代码来打开某个页面,并读取对应的内容。但我之前提到过,我们的程序要能自动保存、发邮件通知你等等,这些功能我都已经放在 start_kayak 函数里面啦!

首先,你需要指定出发/到达的城市和乘机日期。接着,程序将会根据你输入的数据,构造对应的 kayak 地址字符串,再打开这个网页地址——这会直达“最佳”排序的搜索结果页面。在第一次爬取之后,我就悄摸摸地把页面顶部的价格和时间对照表给存了下来。我将用这个表格来计算出最低价格和平均价等数据,和 Kayak 的预测推荐数据(一般在页面的左上角)一起用电子邮件发给你。如果你的搜索不含有弹性日期的话,就不会有一个对照表对象出现在页面上,那么你的这段代码就可能会出问题。

我使用 Outlook 帐户(hotmail)进行了一下电子邮件发送测试。如果你没有 Outlook,也可以在网上搜索到许多替代方案,还有其他方法可以实现。如果您已经拥有 Hotmail 帐户,那你直接替换一下相关的账户信息应该就能用了。

译注:请注意,永远不要把密码直接写在代码里,版本管理系统里的东西是删不掉的。

如果你想要理解这些代码的每个部分到底产生了什么作用,请把它们复制出来,在函数外运行它,观察一下。只有这样,你才能真正理解其中的原理。

把所有代码都用上

在写完了上面这些代码之后,我们需要把这些函数都组装起来,让它们开始工作。

为了保持例子的简单,我们不妨就用一个简单的循环来重复调用它们。在循环的前面,我加了四个“花哨”(并不)的提示,让你可以直接输入出发和到达的城市,以及搜索的日期范围(用的就是 input 函数)。不过在我们测试的时候,我并不想每次都输入这些变量,所以我在下面写入了 4 个测试数据,在实际使用的时候,你只需要把这 4 个测试数据注释掉就好啦。

如果你已经看到了这里,恭喜!你已经学完了今天这个短短的教程。

对于学有余力的读者,可以考虑一下如何改进我们这段简单的小程序,比如我想到的有:使用微信机器人,把搜索结果文字通过微信发给你自己;使用 VPN 或是其他更隐蔽的方式从多个服务器同时获取搜索结果;把保存搜索结果的 Excel 文件作为附件发送;使用更高级的功能来搞定验证码等问题……等等等等。

我总是欢迎大家的建议和意见,有任何想法都可以在下面给我留言!我会尽可能回复的!

责任编辑:

计算飞机票价格python_Python 带你薅羊毛:手把手教你揪出特价机票信息相关推荐

  1. 计算飞机票价格python_python爬取京东机票,监控机票,机票价格分析

    又到了一年一度的国庆假期.今年国庆.中秋撞到一起,所以打算回家过节.从帝都到海口,每到节假日的机票都是全价票-2400大洋的单程票,消费不起呀!所以为了买到便宜点的机票,我就写了一个简单的爬虫来监控机 ...

  2. 安卓10自带录屏_手把手教你安卓手机怎么录屏,收下这份屏幕录制指南

    原标题:手把手教你安卓手机怎么录屏,收下这份屏幕录制指南 记得前两年安卓手机大部分还不支持系统录屏功能的时候,小编有多羡慕使用苹果手机的人.但是近年来安卓系统的功能也在不断发展完善,大部分的安卓手机都 ...

  3. 计算飞机票价格python_完美假期第一步:用Python寻找最便宜的航班!

    大数文摘出品 编译:高延.M.Y.Love.胡笳.蒋宝尚 "你喜欢旅游吗?" 这个简单的问题经常会得到一个积极的回复甚至还会额外收到一个或两个冒险的故事.通常来讲,旅行是一种体验新 ...

  4. 2d抽奖规则_【内含抽奖】万张优惠券送送送,免费影票抽抽抽,万达影城带你薅羊毛!...

    全年免费观影福利 点击下方链接 立即参与 [我们9岁啦]全年免费观影/爆米花/哈根达斯送送送,中原万达影城感恩有你,一路相伴! 今年的双十一比往年来的更早一些 你是否也在预售的直播间熬红了眼? 是否已 ...

  5. python 拼多多_拼多多现重大BUG被“薅羊毛”,教你如何用Python简单褥羊毛

    import time from urllib.parse import parse_qs import requests from bs4 import BeautifulSoup from sel ...

  6. 时间加减计算器_初级会计职称考试不让带计算器?!手把手教你使用机考系统计算器,再不看就晚了!...

    备考初级会计的你是否还执着于做纸质习题?还在一次次的手动计算?偷偷告诉大家,别人已经开始模拟机考答题啦! 又有人问:"机考自带的计算器用不明白怎么办?",别急,奥奥将在本文为大家介 ...

  7. 一篇文章带你认识c语言并手把手教你写出你的第一个程序

    写在前面的自我介绍:某211大学计算机科学与技术专业大一新生,目前在洛谷,牛客,leetcode等网站进行刷题,通过自学熟悉c/c++的语法,目前正在自学算法(努力学习ing),以下文章是本人作为一个 ...

  8. 男子一口气充了90年爱奇艺会员被刑拘,薅羊毛也有基本法则?

    转自:扩展迷EXTFANS 薅羊毛,简单来说,就是通过一些技巧将小的利益批量化,进而取得比较大的效益. 本质上,薅羊毛就是利用互联网平台的促销活动来进行批量操作,或直接利用漏洞来领取福利. 如今互联网 ...

  9. 男子一口气充了90年爱奇艺会员被刑拘,以后还敢薅羊毛吗?

    程序员的成长之路 互联网/程序员/技术/资料共享 关注 阅读本文大概需要 4 分钟. 来自:网络 所谓薅羊毛,简单来说,就是通过一些技巧将小的利益批量化,进而取得比较大的效益. 本质上,薅羊毛就是利用 ...

  10. 镜播无人直播带货教程,手把手教你如何搭建直播间

    镜播无人直播带货新手教程,手把手教你如何搭建直播间 如果你一个人在家里面直播,没有直播中控来辅助你一个情况下怎么办?来教你一个什么叫做镜播.用镜子来做一个直播的辅助.在你的面前放一面镜子,下面是产品, ...

最新文章

  1. Docker入门系列之二:使用dockerfile制作包含指定web应用的镜像
  2. HMM 模型输入数据处理的优雅做法 来自实际项目
  3. 立体匹配算法实现之:AdaptWeight
  4. Groovy与Java的不同点
  5. 最新!全球学术排名出炉:22所中国大学位居世界100强
  6. 拼多多出手了,iPhone 12系列最高降价800元!
  7. VC/MFC中的CComboBox控件使用详解
  8. 服务器控件编程中的控件状态保存机制
  9. WampServer图标黄色如何解决
  10. C++11线程安全的单例模式
  11. 数字图像处理:时域、频域和空间域
  12. 为什么快捷指令无法将媒体转换为文本_刘星文:快捷指令这个神器,让你的 iPhone 好用到飞起...
  13. 微型计算机是以微处理器为基础,在计算机中以微处理器为核心组成的微型计算机属于第几代计算机...
  14. java 拉姆达表达式_Java8中foreach与拉姆达表达式的组合使用
  15. qq音乐html5测试性格,根据你的听歌习惯测试你的性格
  16. 现代化个人博客系统 ModStartBlog v5.7.0 简约纯白主题,富文本大升级
  17. 【刷题】微软高频题总结
  18. python三角形判断_python三角形判定怎么做
  19. python Xarray处理设置2维数组作为coordinates
  20. 【创业说】零经验接手APP运营推广,聊聊这两个月我是怎么熬过来的

热门文章

  1. 工业对5G网络的应用需求和通信过程
  2. 四种常见的颜色模式及各自的特点?
  3. Photoshop将图片转为RGB颜色模式 您还不会吗?
  4. Camera 工程师的披荆斩棘之路
  5. Android自己动手打造XML解析框架
  6. poj1265 -- Area(皮克定理)
  7. 防火墙结构之屏蔽主机体系结构
  8. 软件测试有效性指标,软件测试用例评审有效性的44个衡量标准[1]
  9. 使用wireshark抓包获取湖北电信校园网飞Young宽带客户端加密后的账号密码
  10. twemproxy0.4原理分析-批量操作(mset,hset等)的实现原理