0x0 前言

在疫情发生之前,在知乎上刷到一个回答,说是用服务器实现一个自动爬取机票并将过滤后的机票信息发送到自己的邮箱中,感觉十分有趣。由于疫情原因无法返校,加上家和学校相隔接近3000公里,再加上阿里云送服务器。于是想起了这个idea。

0x1 实现思路

  1. 服务器镜像选择的是centos7.7,个人比较喜欢centos,因为可以直接通过root用户操作。
  2. 爬取机票信息,选取了携程作为数据源。
  3. 存放机票信息,选择Mariadb。
  4. 发送机票信息邮件到我的邮箱。

基于以上选择,在Ubuntu18.04平台上使用python作为开发语言。大概的结构如下图。

0x2 实现过程

  1. 阿里云服务器的领取就比较简单了,不再赘述。

  2. 爬取机票信息需要爬虫,但爬虫对我来说完全是一个全新的知识,事先找了很多资料,发现很多文章都注重如何防止爬虫被识别。但在我的想法中这个项目每隔一天爬取一次数据就足够了。所以在这两个文章的帮助下,成功的完成了爬取机票信息的过程。Python3爬虫基础实战篇之机票数据采集,Python爬取携程机票代码实例。因为对这方面也不是很熟悉,所以在这里就不涉及分析的过程了。

    #coding:utf-8
    #getllist.py
    import log
    import requests
    import getjson
    import json# 获取艺龙机票信息
    def getlylist():data=getjson.getcity() # 获取查询需要的请求数据Departure=str(data[0])    # 出发机场三字码DepartureName=str(data[1])    # 出发城市名Arrival=str(data[2])        # 到达机场三字码ArrivalName=str(data[3])  # 到达城市名DepartureDate=str(data[4])  # 出发日期try:url = "https://www.ly.com/flights/api/getflightlist"headers = {"Cookie":"__ftoken=nH1p4OADy%2BhIyjjDXqd7UuCg97ZyEvHWEuua31Q9G2zMeSBPRpzk3a%2FuWEIc7QlNqnha2ncAIbeEOHQQFPWvlA%3D%3D; __ftrace=efde5834-4ae4-45e4-8f55-8942d7304bf8; _dx_uzZo5y=696e7d7c4d7cb8e724cb154e92fd6d27e1fec611898ed7f00d27f905a63c9f8e672f2a0f; _dx_app_bc4b3ca6ae27747981b43e9f4a6aa769=5ed9ea6bGCjbBOcZ1O2OOVzPEPgKWXPp6E1Dv5g1","User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.44",}data = {"Departure":Departure,"Arrival":Arrival,"GetType":"1","QueryType":"1","DepartureDate":DepartureDate,"DepartureName":DepartureName,"ArrivalName":ArrivalName,"IsBaby":0,"paging":{"cid":"8f272df1-5931-48bb-b5bd-8d5181339194","dataflag":"some"},"DepartureFilter":"","ArrivalFilter":"","flat":"465","plat":"465","isFromKylin":1,"refid":""}html = requests.post(url, headers=headers,data=data).textlog.info("Crawling information from ly succeeded")return htmlexcept: log.error("Failed to crawl information from ly")# 获取携程机票信息
    def getcxlist():data=getjson.getcity() # 获取查询需要的请求数据Departure=str(data[0])    # 出发机场三字码DepartureName=str(data[1])    # 出发城市名Arrival=str(data[2])        # 到达机场三字码ArrivalName=str(data[3])  # 到达城市名DepartureDate=str(data[4])  # 出发日期try:url = "https://flights.ctrip.com/itinerary/api/12808/products"headers = {"Referer":"https://flights.ctrip.com/itinerary/oneway/"+Departure+"-"+Arrival+"?date="+DepartureDate,"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:77.0) Gecko/20100101 Firefox/77.0","Content-Type": "application/json"}data = {"flightWay":"Oneway","classType":"ALL","hasChild":False,"hasBaby":False,"searchIndex":1,"airportParams":[{"dcity":Departure,"acity":Arrival,"dcityname":DepartureName,"acityname":ArrivalName,"date":DepartureDate}],"token":"f08d143d0e4dc48def301a5a04f2ae7b"}html = requests.post(url, headers=headers,data=json.dumps(data)).textlog.info("Crawling information from cx succeeded")return htmlexcept:log.error("Failed to crawl information from cx")
    
  3. 在centos7上用执行yum install mariadb-server安装mariadb,其他配置可以参考centos7 安装Mariadb。表结构如下。

  4. 通过python的smtplib模块发送邮件,可以参考。

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    #smtp.py
    import data
    import log
    import getjson
    import smtplib
    from email.mime.text import MIMEText
    from email.header import Header# 构造邮件正文
    def getmailtext():myresult = data.getfilterdata()text=''formattext="航空公司:%s 航班号:%s 价格:%s \n起飞时间:%s 降落时间:%s \n出发航站楼:%s 到达航站楼:%s 机型:%s\n----------------\n"for x in myresult:stri=formattext % (x[1],x[2],x[3],x[4],x[5],x[6]+x[7]+"航站楼",x[8]+x[9]+"航站楼",x[10])text+=strireturn text# 发送邮件
    def mail():header=getjson.getmailheader()mail_host=header[0]  #设置服务器sender_pass=header[1]   #口令 sender = header[2]receivers = header[3] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱try:message = MIMEText(getmailtext(), 'plain', 'utf-8')message['From'] = Header('<'+sender+'>', 'utf-8')message['To'] =  Header(receivers, 'utf-8')message['Subject'] = "机票信息"smtpObj = smtplib.SMTP_SSL(mail_host, 465)smtpObj.login(sender,sender_pass)smtpObj.sendmail(sender, receivers, message.as_string())log.info("Mail sent successfully")except smtplib.SMTPException:log.error("Unable to send Mail")
    
  5. 剩余模块代码。

    • data.py
    # -*- coding: UTF-8 -*-
    # data.py
    import getlist
    import log
    import mysqldao
    import getjson
    import datetime
    import json
    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')# 存储艺龙机票信息
    def storelydata():try:html = getlist.getcxlist()html = json.loads(html)["body"]for j in html["FlightInfoSimpleList"]:item={"flyOffTime":j["flyOffTime"],"arrivalTime":j["arrivalTime"],"flightNo":j["flightNo"],"price":j["lecp"],"equipmentName":j['equipmentName'],"arriveAirportShortName":j["arriveAirportShortName"],"boardPoint":j["boardPoint"],"originAirportShortName":j["originAirportShortName"],"offPoint":j["offPoint"],"Departure":j["originAirportCode"],"Arrival":j["arriveAirportCode"],}if mysqldao.getbyflightNo(j["flightNo"]):mysqldao.updatetable(item)else:mysqldao.insertdatabase(item)log.info("Data saved successfully")except:log.error("Failed to save data!!!")# 存储携程机票信息
    def storecxdata():try:html = json.loads(getlist.getcxlist()).get('data').get('routeList')#print(html)for j in html:if len(j.get('legs')) == 1:legs = j.get('legs')flight = legs[0].get('flight')characteristic = legs[0].get('characteristic')item={"airlineName":flight["airlineName"],"flyOffTime":flight["departureDate"],"arrivalTime":flight["arrivalDate"],"flightNo":flight["flightNumber"],"price":characteristic["lowestPrice"],"equipmentName":flight['craftTypeName']+'('+flight["craftTypeKindDisplayName"]+')',"arriveAirportShortName":flight["arrivalAirportInfo"]["cityName"]+flight["arrivalAirportInfo"]["airportName"],"boardPoint":flight["arrivalAirportInfo"]["terminal"]["name"],"originAirportShortName":flight["departureAirportInfo"]["cityName"]+flight["departureAirportInfo"]["airportName"],"offPoint":flight["departureAirportInfo"]["terminal"]["name"],"DepartureAirportTlc":flight["departureAirportInfo"]["airportTlc"],"ArrivalAirportTlc":flight["arrivalAirportInfo"]["airportTlc"],}if mysqldao.getbyflightNo(flight["flightNumber"]):mysqldao.updatetable(item)else:mysqldao.insertdatabase(item)log.info("Data saved successfully")except:log.error("Failed to save data!!!")# 从数据库中获取list类型的数据
    def getdata():try:data=mysqldao.gettable()log.info("Read data successfully")return dataexcept:log.error("Failed to read data")# 过滤条件
    def getfilter(condition):ret=Falsefliter=getjson.getfliter()price=fliter[0]date=datetime.datetime.strptime(fliter[1],'%Y-%m-%d %H:%M:%S')Departure=fliter[2]Arrival=fliter[3]if (date.date()==condition[4].date() and date.time()>condition[4].time()):if (price>condition[3] and Departure==condition[11] and Arrival==condition[12]):ret=Truereturn ret# 获取经过过滤的list类型的数据
    def getfilterdata():list = []try:result=mysqldao.gettable()for x in result:if getfilter(x):list.append(x)log.info("Read data successfully")return listexcept:log.error("Failed to read data")
    
    • mysqldao.py
    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    # mysqldao.py
    import mysql.connector
    import log
    import json
    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')
    mydb = mysql.connector.connect(host="localhost",user="root",passwd="admin",database="test"
    )
    mycursor = mydb.cursor()# 构造插入sql语句
    def dic2sqlinsert(dic):sql = "insert into airfare (%s) VALUES (%s);"sf = ''index=''for key in dic:value = str(dic[key])ff= str(key)if key =='price':sf += (value + ',')else:sf += ('"'+value + '",')index+=(ff+',')sf = sf.rstrip(',')index = index.rstrip(',')sql2 = sql % (index,sf)return sql2# 构造更新sql语句
    def dic2sqlupdate(dic):sql="UPDATE airfare SET %s WHERE flightNo='%s';"sf = ''temp=''flightNo=''for key in dic:value = str(dic[key])ff= str(key)if key=='flightNo':flightNo=valuecontinueelif key =='price':sf = (value + ',')else:sf = ('"'+value + '",')temp+=ff+'='+sftemp=temp.rstrip(',')sql=sql % (temp,flightNo)return sql# 执行sql语句
    def executesql(sql):mycursor.execute(sql)# 数据表内容有更新,必须使用到该函数
    def reflashdatabase():mydb.commit()# 增加行
    def insertdatabase(data):sql=dic2sqlinsert(data)executesql(sql) #mycursor.execute(ret)reflashdatabase()# 查询表
    def gettable():executesql("SELECT * FROM airfare")myresult = mycursor.fetchall() return myresult# 通过航班号查询
    def getbyflightNo(flightNo):sql="SELECT * FROM airfare where flightNo='%s'"sql=sql % flightNoexecutesql(sql)myresult = mycursor.fetchall()return myresult# 通过航班号更新
    def updatetable(data):sql=dic2sqlupdate(data)executesql(sql)reflashdatabase()
    
    • getjson.py
    #coding:utf-8
    import json
    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')# 获取发送邮件的用户和接受的用户
    def getmailheader():with open('/root/project/program/json/email.json', 'r') as f:   #根据自身的路径填入绝对路径header = json.load(f)tup = (header["mail_host"],header["sender_pass"],header["sender"],header["receivers"])return tup# 获得需要爬取的机票的数据
    def getcity():with open('/root/project/program/json/crawl.json', 'r') as f:crawl = json.load(f)tup = (crawl["Departure"],crawl["DepartureName"],crawl["Arrival"],crawl["ArrivalName"],crawl["DepartureDate"])return tup# 获得过滤的条件
    def getfliter():with open('/root/project/program/json/fliter.json', 'r') as f:fliter= json.load(f)tup = (fliter["price"],fliter["flyOffTime"],fliter["Departure"],fliter["Arrival"])return tup
    
    • log.py
    #coding:utf-8
    import logging
    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')
    LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
    DATE_FORMAT = "%m/%d/%Y %H:%M:%S %p"
    logging.basicConfig(filename='/root/project/program/log/info.log', level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMAT) #写入绝对路径# 记录运行信息
    def info(text):logging.info(text)# 记录错误信息
    def error(text):logging.error(text)
    
  6. 文件存放结构

    json文件夹中存放相应的.json文件,log文件夹中存放日志信息,program文件夹中放入python脚本。

  7. 选择Linux Crontab来触发查询和发送邮件。执行contab -e写入

    30 6 * * * /usr/bin/python /root/project/program/mail.py #每天的早上6:30发送邮件
    10 6 * * * /usr/bin/python /root/project/program/get.py  #每天的早上6:10爬取数据
    

    因为contab不会继承环境变量所以要写入绝对路径

    • mail.py
    #coding:utf-8
    import data
    data.storecxdata()
    
    • get.py
    #coding:utf-8
    import smtp
    smtp.mail()
    
  8. 测试结果。

0x3 总结

通过这个项目了解到了爬虫的实现原理,感觉还是很不错的。这个项目的代码写的还是有点乱的,文件的存放也不是很规范。按需食用。

python爬取携程机票并发送过滤后的机票信息到指定邮箱相关推荐

  1. python中飞机票购买程序_「最低折扣机票查询」Python 爬取携程所有机票找出最低折扣机票,让你无忧回家过年 - seo实验室...

    最低折扣机票查询 前言 对于平时出行大多数人都是选择坐高铁,当然了如果这是对于距离比较近的行程是最划算的,如果对于路途长远的人言,提前购买飞机票价格可能比高铁票更加的便宜,如果我们可以爬取机票数据并分 ...

  2. Python爬取携程机票代码实例

    Python爬取携程机票代码实例 现在携程的页面是通过接口传递数据的,不能直接使用xpath进行解析,需要模拟调用接口的步骤 dcity是指出发地的城市编码 acity是指目的地的城市编码 其他参数是 ...

  3. python爬取携程旅游评价信息词云图分析

    python爬取携程旅游评价信息词云图分析 前言 前面咱们已经分析过如何爬取携程旅游的相关信息,如果没有看过的,可以先跳转看一下前面的那篇博客:python 爬虫 一键爬取携程旅游团数据 这一篇呢,咱 ...

  4. python 爬取携程旅游景点评论

    python爬取携程旅游景点评论 爬取网址:https://you.ctrip.com/ 爬取评论全部代码 import requests import json import re import t ...

  5. python 携程登陆,Python爬取携程信息

    python爬取携程景点评论信息 今天要分析的网站是携程网,获取景点的用户评论,评论信息通过json返回API,页面是这个样子的 按下F12之后,F5刷新一下 具体需要URL Request的方式为P ...

  6. python爬取携程景区用户评论

    python爬取携程景区用户评论(爬虫时遇到翻页但url不变问题时的解决办法) python爬取携程景区用户评论 Ajax页面加载的页面和普通页面的不同 解决办法 效果 python爬取携程景区用户评 ...

  7. python爬取携程酒店信息_不写代码玩转爬虫实例(3) - 抓取携程酒店信息

    背景需求 有不少朋友问永恒君携程网站的酒店信息怎么抓取,今天这篇文章来分享一下使用web scraper来快速实现抓取携程酒店信息. 例如,在携程官网搜索北京 密云水库的酒店信息, 可以搜索到非常多的 ...

  8. Python 爬取携程所有机票

    打开携程网,查询机票,如广州到成都. 这时网址为:http://flights.ctrip.com/booking/CAN-CTU-day-1.html?DDate1=2018-06-15 其中,CA ...

  9. Python爬取携程酒店信息

    文章目录 前言 一.请求头,请求参数 二.获取JSON数据 总结 前言 还是毕设- 要用到哈尔滨黑河酒店的数据 但每个城市都一样 还是从携程下手- 一.请求头,请求参数 在携程主页搜索我们要爬取的城市 ...

最新文章

  1. ECEF rectangular coordinate system(ECEF直角坐标系)
  2. PyCharm与git/GitHub取消关联
  3. 增加XP的IIS连接数,解决403.9连接用户过多的问题
  4. 使用Dockerfile构建SpringBoot应用镜像
  5. 玩转C#控件-常用控件属性
  6. nyist 17 -----记忆式搜索------Accept
  7. java按照io流向基类_Java IO详解
  8. cocos2dx 开发成长之路 004
  9. 智引IT综合管理解决方案
  10. java 静态导入 import static
  11. Cesium 纹理贴图
  12. Linux嵌入式所有知识点-思维导图-【一口君吐血奉献】
  13. 无需任何软件,简单修改Win7开机登陆界面背景图片
  14. c++通讯录管理系统(函数,指针,结构体)
  15. 重装win10之后谷歌chrome浏览器字体模糊的问题
  16. 阿里云视频点播(上传视频)服务最新版本使用方法(解决部分依赖无法下载或不存在问题)
  17. panda是删除行_pandas删除包含指定内容的行
  18. ubuntu下git搭建服务器(gitosis)
  19. 国二c语言程序,国家计算机二级c语言题库及答案
  20. 人工智能助力未来教育

热门文章

  1. TP框架下设置静态资源缓存
  2. jsp页面兼容谷歌浏览器相关问题
  3. 飞思卡尔单片机AD模块简述(1)
  4. f_GetErrorInfo()获取系统错误信息函数
  5. n元(维)正态分布(The multivariate normal distribution)
  6. VMWARE下的Ubuntu清理磁盘
  7. 2021年3月面试题集合
  8. IPFS生态日趋成熟,Filenet携手Filecoin共同打造共享存储新格局
  9. STM32HAL----红外遥控(NEC)
  10. eclipse开发jsp网站