鱼C论坛

 找回密码
 立即注册
查看: 2464|回复: 5

set去重失败

[复制链接]
发表于 2016-11-27 20:09:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 ycgzs98789 于 2016-11-27 20:14 编辑

写这个程序的目的是为了监控12306什么时候卖票,好抢票回家

但是发现查询出来的结果有好多重复的车次信息,使用list转set再转回来还是有重复信息,求各位帮助


list定义在97行,转set在136行,list增加数据在170行

  1. import requests
  2. import re
  3. import itertools
  4. import json
  5. import smtplib
  6. from email.mime.text import MIMEText
  7. import logging
  8. import time
  9. import random

  10. logging.basicConfig(level=logging.INFO,
  11.                     format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
  12.                     datefmt='%a, %d %b %Y %H:%M:%S',
  13.                     filename='./'+time.strftime('%Y-%m-%d', time.localtime())+'test.log',
  14.                     filemode='w')


  15. url_base = "https://kyfw.12306.cn"
  16. url_init = "https://kyfw.12306.cn/otn/leftTicket/init"
  17. url_query_base = "https://kyfw.12306.cn/otn/leftTicket/queryX?" \
  18.                  "leftTicketDTO.train_date=%s" \
  19.                  "&leftTicketDTO.from_station=%s" \
  20.                  "&leftTicketDTO.to_station=%s" \
  21.                  "&purpose_codes=ADULT"

  22. table_html = '''<table align="center">
  23.     <tbody align="center">
  24.     <tr>
  25.         <th>车次</th>
  26.         <th>是否始发站发车</th>
  27.         <th>起始站</th>
  28.         <th>发车时间</th>
  29.         <th>抵达</th>
  30.         <th>到达时间</th>
  31.         <th>历时</th>
  32.         <th>当前最晚售票日期</th>
  33.         <th>售票按钮文字</th>
  34.     </tr>
  35.     @
  36.     </tbody>
  37. </table>
  38. '''

  39. headers = {
  40.     'Accept': '*/*',
  41.     'Accept-Encoding': 'gzip, deflate, sdch, br',
  42.     'Accept-Language': 'zh-CN,zh;q=0.8',
  43.     'Cache-Control': 'no-cache',
  44.     'Connection': 'keep-alive',
  45.     'Host': 'kyfw.12306.cn',
  46.     'If-Modified-Since': '0',
  47.     'Referer': 'https://kyfw.12306.cn/otn/leftTicket/init',
  48.     'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36',
  49.     'X-Requested-With': 'XMLHttpRequest'}

  50. #站点编码文件
  51. file_station_name = "station_name.txt"
  52. from_station = "杭州"
  53. to_station = "三门峡"
  54. train_date = '2017-01-22'

  55. user_addr = '*****@163.com'
  56. user_password = '*****'
  57. user_host = 'smtp.163.com'

  58. to_list = ['*****@qq.com']

  59. session = requests.Session()


  60. class Ticket(object):
  61.     __slots__ = ('station_train_code', 'is_start_station', 'from_station_name', 'start_time', 'to_station_name', 'arrive_time', 'lishi', 'control_train_day', 'buttonTextInfo')  # 用tuple定义允许绑定的属性名称
  62.     def __init__(self, station_train_code, is_start_station, from_station_name, start_time,
  63.                  to_station_name, arrive_time, lishi, control_train_day, buttonTextInfo):
  64.         self.station_train_code = station_train_code
  65.         self.is_start_station = is_start_station
  66.         self.from_station_name = from_station_name
  67.         self.start_time = start_time
  68.         self.to_station_name = to_station_name
  69.         self.arrive_time = arrive_time
  70.         self.lishi = lishi
  71.         self.control_train_day = control_train_day
  72.         self.buttonTextInfo = buttonTextInfo

  73.     def to_str(self):
  74.         return "<tr><td>" + self.station_train_code\
  75.         + "</td><td>" + self.is_start_station \
  76.         + "</td><td>" + self.from_station_name \
  77.         + "</td><td>" + self.start_time \
  78.         + "</td><td>" + self.to_station_name \
  79.         + "</td><td>" + self.arrive_time \
  80.         + "</td><td>" + self.lishi\
  81.         + "</td><td>" + self.control_train_day \
  82.         + "</td><td>" + self.buttonTextInfo + "</td></th>"

  83. list_ticket = []

  84. def query(url):
  85.     resp, resp_html = open_url(url)

  86.     pattern = re.compile(r'<script.{,200}<\/script>', re.I | re.M)
  87.     lists = pattern.findall(resp_html)
  88.     # station_name是车站名与编号的对应文件
  89.     url_station_name = url_base + lists[-4].split('"')[3]
  90.     resp, content_station_name = open_url(url_station_name)

  91.     content_station_name = content_station_name[20:-2]

  92.     if resp.status_code is 200:
  93.         with open(file_station_name, 'w', encoding='UTF-8') as f:
  94.             f.truncate()
  95.             f.write(content_station_name)
  96.             logging.info('修改%s文件' % file_station_name)
  97.     else:
  98.         logging.error('获取%s文件失败,status_code = %s' % (file_station_name, resp.status_code))

  99.     list_from_station = query_station(staion_name=from_station, file_content=content_station_name)
  100.     list_to_station = query_station(staion_name=to_station, file_content=content_station_name)
  101.     if (len(list_from_station) > 0) and (len(list_to_station) > 0):
  102.         # 计数,有时候会出现请求错误、或没有相应直达车次,连续失败3次后跳过
  103.         global count
  104.         count = 0

  105.         # 笛卡尔积
  106.         for x in itertools.product(list_from_station, list_to_station):
  107.             url_query = url_query_base % (train_date, x[0], x[1])
  108.             # 不等于0说明失败
  109.             while count < 3:
  110.                 count = query_ticket(url_query, x, count)

  111.             count = 0
  112.             continue

  113.         content_email = ''
  114.         for ticket in list(set(list_ticket)): # 就是这里错了,转换后还是有重复数据
  115.             content_email += ticket.to_str()
  116.         send_simple_txt_email(to_list,title="每日12306监控邮件", content=table_html.replace('@', content_email))

  117.         count = 0

  118.     else:
  119.         send_simple_txt_email(to_list,title="站点编码转换错误", content="出发地:%s \t 目的地:%s" % (from_station, to_station))
  120.         pass

  121.     pass

  122. # 访问车票查询URL, x是替换参数, count用来3次计数
  123. def query_ticket(url_query, x, count):
  124.     query_resp, query_resp_html = open_url(url_query)
  125.     query_json = json.loads(query_resp_html)

  126.     ++count
  127.     if query_json['status']:  # JSON状态status为True
  128.         logging.error("查询车票JSON状态正确:%s" % query_json)
  129.         if query_json['messages']:  # 出现异常情况
  130.             logging.error("查询车票出现messages信息:%s" % query_json['messages'])
  131.         elif (not query_json['data']) or (len(query_json['data']) == 0):
  132.             logging.info("查询车票出现data信息为空,可能没有直达车次")
  133.         else:
  134.             count = 0

  135.             logging.info("查询车票信息结构正确")
  136.             for data_l in query_json['data']:
  137.                 # type(data_l) = <class 'dict'>

  138.                 data_ll = data_l['queryLeftNewDTO']
  139.                
  140.                 #加到list中
  141.                 list_ticket.append(Ticket(data_ll['station_train_code'], (data_ll['from_station_name'] == data_ll['start_station_name'] and "是" or "否"),
  142.                                       data_ll['from_station_name'], data_ll['start_time'], data_ll['to_station_name'], data_ll['arrive_time'],
  143.                                       data_ll['lishi'], data_ll['control_train_day'], str(data_l['buttonTextInfo']).replace('<br/>', '')))

  144.             # 查询成功,跳出循环
  145.             count = 3

  146.     else:
  147.         logging.error("JSON返回结果错误,URL = %s \n \t JSON = %s" % (url_query, query_json))

  148.     return count

  149. # html邮件
  150. def send_simple_txt_email(to_user_list=to_list, title='这是标题呀', content='这是内容呀'):
  151.     pass
  152.     msg = MIMEText(_text=content, _subtype='html', _charset='gb2312')
  153.     msg['Subject'] = title
  154.     msg['From'] = user_addr
  155.     msg['To'] = ";".join(to_user_list)

  156.     logging.info('开始发送邮件')
  157.     try:
  158.         server = smtplib.SMTP()
  159.         server.connect(host=user_host)
  160.         server.login(user_addr, user_password)
  161.         server.sendmail(from_addr=user_addr, to_addrs=to_user_list, msg=msg.as_string())
  162.         server.close()
  163.         logging.info("发送邮件成功")
  164.         return True
  165.     except Exception as e:
  166.         logging.error('发送邮件出现异常,请检查')
  167.         logging.error(e)
  168.         print(e)
  169.         return False

  170. # 查询对应的站点对应编码,list格式
  171. def query_station(staion_name, file_content):
  172.     list_content = file_content.split('@')
  173.     list_result = []
  174.     for content in list_content:
  175.         if staion_name in content:
  176.             list_result.append(content.split('|')[2])
  177.     if list_result is None or len(list_result) == 0:
  178.         logging.info("站点:%s 转换结果为空" % staion_name)
  179.     else:
  180.         logging.info("站点:%s 转换成功" % staion_name)
  181.     return list_result


  182. # 返回resp和html编码
  183. def open_url(url):
  184.     #随机睡眠2-5秒
  185.     time.sleep(random.randint(2, 5))
  186.     # 关闭验证
  187.     resp = session.get(url=url, headers=headers, verify=False)
  188.     resp_html = resp.text.encode(resp.encoding).decode('utf-8')
  189.     logging.info("访问url:%s" % url)
  190.     return resp, resp_html


  191. def main():
  192.     query(url_init)

  193. if __name__ == '__main__':
  194.     main()
复制代码
QQ截图20161127201324.png
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2016-11-27 23:42:35 | 显示全部楼层
大神
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2016-11-28 08:46:53 | 显示全部楼层

python学了1个半月,再懒一点,看看有什么东西是程序可以代替的,也就能到我这样了
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-11-28 09:53:53 | 显示全部楼层
自己解决
去重失败原因:每次我在往list中添加数据的时候,都是新建的类对象,id不一致
因此增加一个list_ticket_id对象,用来以str形式存储车次号,如果已存在,说明有重复数据

不过我担心以后如果改成查询多个站点到多个站点,就会出现缺失数据的现象,难道只能改成 车次+发车时间+到站时间 吗?有没有其他方法?

另外,因为我在list和set中添加类对象时去重失败,有两个问题需要问一下
1.set去重是根据什么去的?id吗?
2.if a in list: in也是根据id判断的吗?

完整修复代码如下
  1. import requests
  2. import re
  3. import itertools
  4. import json
  5. import smtplib
  6. from email.mime.text import MIMEText
  7. import logging
  8. import time
  9. import random

  10. logging.basicConfig(level=logging.INFO,
  11.                     format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
  12.                     datefmt='%a, %d %b %Y %H:%M:%S',
  13.                     filename='./' + time.strftime('%Y-%m-%d', time.localtime()) + 'test.log',
  14.                     filemode='w')

  15. url_base = "https://kyfw.12306.cn"
  16. url_init = "https://kyfw.12306.cn/otn/leftTicket/init"
  17. url_query_base = "https://kyfw.12306.cn/otn/leftTicket/queryX?" \
  18.                  "leftTicketDTO.train_date=%s" \
  19.                  "&leftTicketDTO.from_station=%s" \
  20.                  "&leftTicketDTO.to_station=%s" \
  21.                  "&purpose_codes=ADULT"

  22. table_html = '''<table align="center">
  23.     <tbody align="center">
  24.     <tr>
  25.         <th>车次</th>
  26.         <th>是否始发站发车</th>
  27.         <th>起始站</th>
  28.         <th>发车时间</th>
  29.         <th>抵达</th>
  30.         <th>到达时间</th>
  31.         <th>历时</th>
  32.         <th>当前最晚售票日期</th>
  33.         <th>售票按钮文字</th>
  34.     </tr>
  35.     @
  36.     </tbody>
  37. </table>
  38. '''

  39. headers = {
  40.     'Accept': '*/*',
  41.     'Accept-Encoding': 'gzip, deflate, sdch, br',
  42.     'Accept-Language': 'zh-CN,zh;q=0.8',
  43.     'Cache-Control': 'no-cache',
  44.     'Connection': 'keep-alive',
  45.     'Host': 'kyfw.12306.cn',
  46.     'If-Modified-Since': '0',
  47.     'Referer': 'https://kyfw.12306.cn/otn/leftTicket/init',
  48.     'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36',
  49.     'X-Requested-With': 'XMLHttpRequest'}

  50. # 站点编码文件
  51. file_station_name = "station_name.txt"
  52. from_station = "杭州"
  53. to_station = "三门峡"
  54. train_date = '2017-01-22'

  55. user_addr = '*****@163.com'
  56. user_password = '*****'
  57. user_host = 'smtp.163.com'
  58. to_list = ['*****@qq.com']

  59. session = requests.Session()
  60. list_ticket = []
  61. list_ticket_id = []

  62. class Ticket(object):
  63.     __slots__ = (
  64.         'station_train_code', 'is_start_station', 'from_station_name', 'start_time', 'to_station_name', 'arrive_time',
  65.         'lishi', 'control_train_day', 'buttonTextInfo')  # 用tuple定义允许绑定的属性名称

  66.     def __init__(self, station_train_code, is_start_station, from_station_name, start_time,
  67.                  to_station_name, arrive_time, lishi, control_train_day, buttonTextInfo):
  68.         self.station_train_code = station_train_code
  69.         self.is_start_station = is_start_station
  70.         self.from_station_name = from_station_name
  71.         self.start_time = start_time
  72.         self.to_station_name = to_station_name
  73.         self.arrive_time = arrive_time
  74.         self.lishi = lishi
  75.         self.control_train_day = control_train_day
  76.         self.buttonTextInfo = buttonTextInfo

  77.     def to_str(self):
  78.         return "<tr><td>" + self.station_train_code \
  79.                + "</td><td>" + self.is_start_station \
  80.                + "</td><td>" + self.from_station_name \
  81.                + "</td><td>" + self.start_time \
  82.                + "</td><td>" + self.to_station_name \
  83.                + "</td><td>" + self.arrive_time \
  84.                + "</td><td>" + self.lishi \
  85.                + "</td><td>" + self.control_train_day \
  86.                + "</td><td>" + self.buttonTextInfo + "</td></tr>"




  87. def query(url):
  88.     resp, resp_html = open_url(url)

  89.     pattern = re.compile(r'<script.{,200}<\/script>', re.I | re.M)
  90.     lists = pattern.findall(resp_html)
  91.     # station_name是车站名与编号的对应文件
  92.     url_station_name = url_base + lists[-4].split('"')[3]
  93.     resp, content_station_name = open_url(url_station_name)

  94.     content_station_name = content_station_name[20:-2]

  95.     if resp.status_code is 200:
  96.         with open(file_station_name, 'w', encoding='UTF-8') as f:
  97.             f.truncate()
  98.             f.write(content_station_name)
  99.             logging.info('修改%s文件' % file_station_name)
  100.     else:
  101.         logging.error('获取%s文件失败,status_code = %s' % (file_station_name, resp.status_code))

  102.     list_from_station = query_station(staion_name=from_station, file_content=content_station_name)
  103.     list_to_station = query_station(staion_name=to_station, file_content=content_station_name)
  104.     if (len(list_from_station) > 0) and (len(list_to_station) > 0):
  105.         # 计数,有时候会出现请求错误、或没有相应直达车次,连续失败3次后跳过
  106.         global count
  107.         count = 0

  108.         # 笛卡尔积
  109.         for x in itertools.product(list_from_station, list_to_station):
  110.             url_query = url_query_base % (train_date, x[0], x[1])
  111.             # 不等于0说明失败
  112.             while count < 3:
  113.                 count = query_ticket(url_query, x, count)

  114.             count = 0
  115.             continue

  116.         content_email = ''
  117.         for ticket in list(set(list_ticket)):  # 就是这里错了,转换后还是有重复数据
  118.             content_email += ticket.to_str()
  119.         send_simple_txt_email(to_list, title="每日12306监控邮件", content=table_html.replace('@', content_email))

  120.         count = 0

  121.     else:
  122.         send_simple_txt_email(to_list, title="站点编码转换错误", content="出发地:%s \t 目的地:%s" % (from_station, to_station))
  123.         pass

  124.     pass


  125. # 访问车票查询URL, x是替换参数, count用来3次计数
  126. def query_ticket(url_query, x, count):
  127.     query_resp, query_resp_html = open_url(url_query)
  128.     query_json = json.loads(query_resp_html)

  129.     ++count
  130.     if query_json['status']:  # JSON状态status为True
  131.         logging.error("查询车票JSON状态正确:%s" % query_json)
  132.         if query_json['messages']:  # 出现异常情况
  133.             logging.error("查询车票出现messages信息:%s" % query_json['messages'])
  134.         elif (not query_json['data']) or (len(query_json['data']) == 0):
  135.             logging.info("查询车票出现data信息为空,可能没有直达车次")
  136.         else:
  137.             count = 0

  138.             logging.info("查询车票信息结构正确")
  139.             for data_l in query_json['data']:
  140.                 # type(data_l) = <class 'dict'>

  141.                 data_ll = data_l['queryLeftNewDTO']

  142.                 # 加到list中
  143.                 if data_ll['station_train_code'] in list_ticket_id:
  144.                     continue
  145.                 else:
  146.                     list_ticket_id.append(data_ll['station_train_code'])
  147.                     ticket = Ticket(data_ll['station_train_code'], (
  148.                         data_ll['from_station_name'] == data_ll['start_station_name'] and "是" or "否"),
  149.                                     data_ll['from_station_name'], data_ll['start_time'],
  150.                                     data_ll['to_station_name'], data_ll['arrive_time'],
  151.                                     data_ll['lishi'], data_ll['control_train_day'],
  152.                                     str(data_l['buttonTextInfo']).replace('<br/>', ''))
  153.                     list_ticket.append(ticket)

  154.             # 查询成功,跳出循环
  155.             count = 3

  156.     else:
  157.         logging.error("JSON返回结果错误,URL = %s \n \t JSON = %s" % (url_query, query_json))

  158.     return count


  159. # html邮件
  160. def send_simple_txt_email(to_user_list=to_list, title='这是标题呀', content='这是内容呀'):
  161.     pass
  162.     msg = MIMEText(_text=content, _subtype='html', _charset='gb2312')
  163.     msg['Subject'] = title
  164.     msg['From'] = user_addr
  165.     msg['To'] = ";".join(to_user_list)

  166.     logging.info('开始发送邮件')
  167.     try:
  168.         server = smtplib.SMTP()
  169.         server.connect(host=user_host)
  170.         server.login(user_addr, user_password)
  171.         server.sendmail(from_addr=user_addr, to_addrs=to_user_list, msg=msg.as_string())
  172.         server.close()
  173.         logging.info("发送邮件成功")
  174.         return True
  175.     except Exception as e:
  176.         logging.error('发送邮件出现异常,请检查')
  177.         logging.error(e)
  178.         print(e)
  179.         return False


  180. # 查询对应的站点对应编码,list格式
  181. def query_station(staion_name, file_content):
  182.     list_content = file_content.split('@')
  183.     list_result = []
  184.     for content in list_content:
  185.         if staion_name in content:
  186.             list_result.append(content.split('|')[2])
  187.     if list_result is None or len(list_result) == 0:
  188.         logging.info("站点:%s 转换结果为空" % staion_name)
  189.     else:
  190.         logging.info("站点:%s 转换成功" % staion_name)
  191.     return list_result


  192. # 返回resp和html编码
  193. def open_url(url):
  194.     # 随机睡眠2-5秒
  195.     time.sleep(random.randint(2, 5))
  196.     # 关闭验证
  197.     resp = session.get(url=url, headers=headers, verify=False)
  198.     resp_html = resp.text.encode(resp.encoding).decode('utf-8')
  199.     logging.info("访问url:%s" % url)
  200.     return resp, resp_html


  201. def main():
  202.     query(url_init)


  203. if __name__ == '__main__':
  204.     main()
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2016-11-28 10:29:56 | 显示全部楼层
好贴 mark一下
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-11-28 14:07:13 | 显示全部楼层
我去重开一贴,现在感觉问题和标题关系不大了
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2026-2-24 05:11

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表