[网络爬虫|smtp协议|python]东方财富网爬虫,python smtp协议发送爬取数据至QQ邮箱

本文改自
[网络爬虫|smtp协议|python]东方财富网爬虫,python smtp协议发送爬取数据至QQ邮箱

之前写的爬虫单子,代码已经跑了快3个月了,后续又增加了一些需求,修改了一些小bug

如果需要学习的话,上面的链接文章有分析过程,涉及的一些参数,上面博文也有说明

改动:

  1. 修复了爬虫过程中,qq邮箱发送邮件频率过快,帐号暂时封禁导致的程序异常退出
  2. 修复了程序定时显示时间错误问题
  3. 增加了股票涨幅限制参数
  4. 取消爬取重复数据发送
  5. 限制主力资金范围
  6. 剔除股票代码开头号(如,不需要300,688开头的)
  7. 剔除股票名称包含关键词(如,不需要包含’ST’的股票)
  8. 修复了已知问题

所有的参数修改,均 main 函数下面修改
问题解释:

  1. 发送qq邮箱可添加多个邮箱,在 senders中的列表里添加,第一个参数是授权码,2为邮箱。关于获取授权码及其它一系列问题,均在上述博文中说明过,本文不再重复
  2. 股票涨幅限制为范围限制。如果不需要,可设置参数范围较大,或自行修改程序代码。注释掉参数,就会报错
  3. 获取的每个股票数据,只发送一次
  4. 主力资金范围同3
  5. 剔除股票开头,可设置多个。如果不需要,可以删除列表里面的股票代码开头。当输入多个开头时,请保证所有开头的长度一致。如[300, 688]正确,[300, 68]错误。股票代码开头以第一个参数长度为准。长度不同会造成股票无法限制,但不会报错
  6. 股票名称关键词,如果不需要,可删除内容,但不要注释掉
  7. 修复已知问题,代码能持续运行,捕获异常。如果您发现程序未知错误导致异常退出,请在评论区指出。
# -*- coding: UTF-8 -*-
import random
import sys
import pyperclip
import requests
import re
import json
import time
import pandas as pd
import smtplib
from email.header import Header
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart

'''
需求:
    http://data.eastmoney.com/zjlx/detail.html
    前50,主力占比16.4以上,涨幅4.4以上,超大单占比9.4以上,成交量占前五日平均值的的0.6-1.1,换手率不超8
    参数我可以自行修改,出现过的就不用再出现了,邮件或其他方式提醒,实时的。
'''

'''
参数说明:
    f2: 最新价     f3: 今日涨跌幅    f12: 股票代码   f14: 股票名称
    今日主力净流入:
        f62: 净额     f184
    今日超大单净流入:
        f66: 净额     f69: 净占比
    今日大单净流入:
        f72: 净额     f75: 净占比
    今日中单净流入:
        f78: 净额     f81: 净占比
    今日小单净流入:
        f84: 净额     f87: 净占比
    
    f168: 换手率
    f47: 成交量
    f124
'''

'''
        f47数据不对 113行
'''

# requests-exceptions-sslerror  : dh key too small
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += 'HIGH:!DH:!aNULL'
try:
    requests.packages.urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST += 'HIGH:!DH:!aNULL'
except AttributeError:
    # no pyopenssl support used / needed / available
    pass

# disable warnings
requests.packages.urllib3.disable_warnings()


class Variable:
    def __init__(self, zhuli, zhangfu, chaodadan, chengjiaoliang:list, huanshoulv, num, codehead, zhuliarea, namehead):
        self.zhuli = zhuli
        self.zhangfu = zhangfu
        self.chaodadan = chaodadan
        self.chengjiaoliang = chengjiaoliang
        self.huanshoulv = huanshoulv
        self.num = num
        self.all = []
        self.head = codehead
        self.namehead = namehead
        self.zhuliarea = zhuliarea
        # self.now = time.localtime().tm_mon*30 + time.localtime().tm_mday      # 取近五日的,不用判断时间
        self.ua = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36",
                   # "Host":"push2.eastmoney.com",
                   "Connection":"keep-aliv",
                   "Cache-Control":"max-age=0"}

class Spider:
    def __init__(self, Vb:Variable):
        self.url = "http://data.eastmoney.com/zjlx/detail.html"
        self.HomePageUrl = "http://push2.eastmoney.com/api/qt/clist/get?cb=jQuery112304456859063185632_1636024376116&fid=f62&po=1&pz={pz}&pn=1&np=1&fltt=2&invt=2&ut=b2884a393a59ad64002292a3e90d46a5&fs=m%3A0%2Bt%3A6%2Bf%3A!2,m%3A0%2Bt%3A13%2Bf%3A!2,m%3A0%2Bt%3A80%2Bf%3A!2,m%3A1%2Bt%3A2%2Bf%3A!2,m%3A1%2Bt%3A23%2Bf%3A!2,m%3A0%2Bt%3A7%2Bf%3A!2,m%3A1%2Bt%3A3%2Bf%3A!2&fields=f12,f14,f2,f3,f62,f184,f66,f69,f72,f75,f78,f81,f84,f87,f204,f205,f124,f1,f13"
        self.klineApi = "http://push2his.eastmoney.com/api/qt/stock/kline/get?fields1=f1,f2,f3,f4,f5&fields2=f56&fqt=0&end=29991010&ut=fa5fd1943c7b386f172d6893dbfba10b&cb=jQuery1123038436182619159487_1636971507184&klt=101&secid={f13}.{f12}&fqt=1&lmt=6"
        self.f47Api = "http://push2.eastmoney.com/api/qt/stock/get?fltt=2&invt=2&secid={f13}.{f12}&fields=f47,f168,f43,f46&ut=b2884a393a59ad64002292a3e90d46a5&cb=jQuery11230690409531564167_1636970393200"
        self.vb = Vb
        self.namelist = []
        self.retry = 0
        self.copy = False

    def HomePageGet(self):      # 获取主页数据
        while True:
            response = self.request(self.HomePageUrl.format(pz=self.vb.num))
            if isinstance(response, bool):
                time.sleep(10)
            else:
                break
        # 获取 data 列表
        return self.dataGet(response)['diff']

    def request(self, url):
        while True:
            try:
                response = requests.get(url, headers=self.vb.ua, verify=False, timeout=2)
                self.vb.retry = 0
                return response
            except:
                self.vb.retry += 1
                if self.vb.retry == 3:
                    self.vb.retry = 0
                    return False

    # 前50,主力占比16.4以上,涨幅4.4以上,超大单占比9.4以上,换手率不超8%,成交量占前五日平均值的0.6-1.1
    # 成交量不能筛选,得通过后续请求
    # 初次筛选
    def FirstClean(self, diff:list):        # 主页数据清洗
        nowdata = []
        # 空数据
        if diff[0]['f184'] == '-':
            return nowdata
        try:
            headlength = len(str(self.vb.head[0]))
        except:
            headlength = False
        for i in diff:
            if i['f184']>self.vb.zhuli:     # 主力占比
                if i['f3']>self.vb.zhangfu[0] and i['f3']<self.vb.zhangfu[1]:     # 涨幅
                    if i['f69']>self.vb.chaodadan:      # 超大单
                        # 股票开头和股票名称
                        if headlength and self.vb.namehead!=None:
                            if (int(i['f12'][:headlength]) not in self.vb.head) and self.vb.namehead not in i['f14']:
                                nowdata.append(i)
                        elif headlength:
                            if int(i['f12'][:headlength]) not in self.vb.head:
                                    nowdata.append(i)
                        elif self.vb.namehead!=None:
                            if self.vb.namehead not in i['f14']:
                                nowdata.append(i)
                        else:
                            nowdata.append(i)
        try:
            now = []
            for i in nowdata:
                if i['f62']>self.vb.zhuliarea[0] and i['f62']<self.vb.zhuliarea[1]:
                    now.append(i)
            return now
        except:
            return nowdata

    # 筛选成交量占前五日平均值的 0.6-1.1,需要请求前五日的
    '''
        成交量api:https://push2his.eastmoney.com/api/qt/stock/kline/get?fields1=f1&fields2=f55&fqt=0&end=29991010&klt=101&secid={f13}.{f12}&lmt=5
    '''
    # 筛选占前五日0.6-1.1
    # HomePageUrl 请求不到 f47 数据,需要单独请求
    def SecondClean(self, data:list):
        # 请求 k 线图数据,需要更改 secid,
        # secid 由两部份组成, f13.f12
        result = []
        for line in data:
            response = self.request(self.klineApi.format(f13=line['f13'], f12=line['f12']))
            if not response:
                continue
            klines = self.dataGet(response)['klines']
            kline = []
            # for k in klines:
            #     kline.append(float(k))
            try:
                for i in range(5):
                    kline.append(float(klines[i]))
            except:
                continue
            fiveday = sum(kline)/5
            # f47
            response = self.request(self.f47Api.format(f13=line['f13'], f12=line['f12']))
            if not response:
                continue
            d = self.dataGet(response)
            if d['f168']/100>=self.vb.huanshoulv:
                continue
            today = d['f47']
            if not (d['f43']>d['f46']):
                print("跳过")
                continue
            # 成交量占前 5 日平均值的 0.6-1.1
            value = today/fiveday
            if (value>=self.vb.chengjiaoliang[0] and value<=self.vb.chengjiaoliang[1]):
                # 股票代码,名称,最新价,今日涨跌幅,今日主力净流入,占比,换手率,现价,开盘价
                result.append([line['f12'], line['f14'], line['f2'], str(line['f3'])+"%", self.Format(line['f62']), str(line['f184'])+"%", str(line['f69'])+'%', "%.2f" % (d['f168']/100) +"%", '%.2f'%value, d['f43'], d['f46']])
        return result

    # 数据判断是否需要保存
    def JudgeSave(self, data:list):
        line1 = [['股票代码', '名称', '最新价', '今日涨跌幅', '今日主力净流入', '今日主力占比', '超大单占比', '换手率', '比值', "现价", "开盘价"]]
        result = []
        # 筛选不重复的保存
        for line in data:
            if line[1] not in self.namelist:
                self.namelist.append(line[1])
                result.append(line)

            # self.namelist.append(line[1])
            # line.insert(0, count)
            # result.append(line)
            # count += 1

        result.sort(key=self.s, reverse=True)
        if len(result)!=0:
            if not self.copy:
                pyperclip.copy(result[0][0])
                self.copy = True
            print("_"*88)
            for i in result:
                print(f"\033[31m{i[0]}\033[0m", end="\t")
                for b in range(1, len(i)):
                    print(i[b], end="\t")
                print("|")
            print(" ̄"*55)
            # 保存文件
            self.vb.all = self.vb.all+result
            excelData = pd.DataFrame(line1+self.vb.all)
            writer = pd.ExcelWriter("股票.xlsx")
            excelData.to_excel(excel_writer=writer, columns=None, index=None, header=False)
            writer.close()
            return True
        else:
            return False

    # 返回 data 字典
    def dataGet(self, response):
        d = re.compile('"data":({.*)}\);')
        data = re.findall(d, response.text)[0]
        data = json.loads(data)
        return data

    def Format(self, data):     # 换成万或者亿
        if len(str(data))>8:        # 换成亿
            return '%.2f' % (data/(10**8)) + '亿'
        elif len(str(data))==8:
            return '%.2f' % (data / (10 ** 7)) + '千万'
        elif len(str(data))>4:
            return '%.2f' % (data / (10 ** 4)) + '万'
        else:
            return str(data)

    def s(self, elem):
        return elem[-4]

    def run(self):
        self.data = self.HomePageGet()
        self.data = self.FirstClean(self.data)
        self.data = self.SecondClean(self.data)
        return self.JudgeSave(self.data.copy())

class QQEmail:
    def __init__(self, senders, senderName, receiver, receiverName):
        self.senders = senders
        self.senderName = senderName
        self.receiver = receiver
        self.receiverName = receiverName

        self.MailLogin()

    # 创建smtp,登录
    def MailLogin(self):
        self.accoutGet()
        try:
            self.server = smtplib.SMTP_SSL(host='smtp.qq.com', port=465)
            response = self.server.login(self.sender, self.qqCode)
            if response[0] == 235:
                print("邮箱登录成功!")
        except Exception as e:
            print("邮箱登录失败!", e)

    def accoutGet(self):
        try:
            self.qqCode, self.sender = random.choice(self.senders.copy().remove((self.qqCode, self.sender)))
        except:
            self.qqCode, self.sender = random.choice(self.senders)


    def MessageBuild(self):
        content = MIMEText(' ')  # 邮件正文内容,需要可填写
        message = MIMEMultipart()
        message.attach(content)  # 附上正文内容
        message['From'] = self.senderName
        message['To'] = self.receiverName
        message['Subject'] = Header('股票', 'utf-8')  # 标题
        xlsx = MIMEApplication(open('股票.xlsx', 'rb').read())
        xlsx['Content-Type'] = 'application/octet-stream'
        xlsx.add_header('Content-Disposition', 'attachment', filename='股票.xlsx')
        message.attach(xlsx)
        return message

    def SendMail(self):
        retry = 0
        while True:
            try:
                self.server.sendmail(from_addr=self.sender, to_addrs=self.receiver, msg=self.MessageBuild().as_string())
                print('已发送! ', end="")
                break
            except smtplib.SMTPException as e:
                print("邮箱登录异常,正在重试!", e)
                self.MailLogin()
                retry += 1
                if retry == 2:
                    print("邮箱登录异常!五分钟后重试")
                    time.sleep(300)
                    retry = 0


class Control(Spider, QQEmail):
    def __init__(self, zhuli, zhangfu, chaodadan, chengjiaoliang, huanshoulv, senders, senderName, receiver, recieverName, num, codehead, zhuliarea, namehead):
        # 变量类
        self.vb = Variable(zhuli, zhangfu, chaodadan, chengjiaoliang, huanshoulv, num, codehead, namehead, zhuliarea)

        # 初始化邮箱类
        self.qqmail = QQEmail(senders, senderName, receiver, recieverName)

        # 爬虫类
        self.spider = Spider(self.vb)

    def main(self, stop, end):
        print("爬虫启动")
        count = 1
        while True:
            send = self.spider.run()
            if send:
                print("获取最新股票数据!", end="")
                self.qqmail.SendMail()
                print(count, "\n\n")
                count += 1
            else:
                pass
            time.sleep(stop)
            if time.localtime().tm_hour == end[0]:
                if time.localtime().tm_min == end[1]:
                    break

def getTime(t:str):
    return [int(a) for a in t.split(":")]

if __name__ == '__main__':
    # 配置参数
    zhuli = 3
    zhangfu = [2.5, 9.88]
    chaodadan = 2
    chengjiaoliang = [0.2, 10]
    # chengjiaoliang = [1, 10]
    huanshoulv = 0.7
    num = 95
    stop = 1
    剔除股票开头 = [30,68]
    剔除名称 = 'ST'
    主力净流入范围 = [10000000, 300000000]


    # 配置邮箱
    senders = [("授权码", "邮箱"),
               ("授权码2", "邮箱2")]
    receiver = '接收邮箱'
    senderName = '发送名'
    receiverName = '接收者'

    # 启动时间
    start = '22:06'
    end = '15:00'

    # 准时启动
    start = getTime(start)
    end = getTime(end)

    # 这段是启动时间,如果注释掉就不限制时间了
    while True:
       print("距离启动还差:{}分钟".format((-time.localtime().tm_hour+start[0])*60-time.localtime().tm_min+start[1]))
       if time.localtime().tm_hour == start[0]:
           if time.localtime().tm_min == start[1]:
               break
       time.sleep(10)

    # 启动
    control = Control(zhuli, zhangfu, chaodadan, chengjiaoliang, huanshoulv, senders, senderName, receiver, receiverName, num, 剔除股票开头, 剔除名称, 主力净流入范围)
    control.main(stop, end)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

竹一笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值