主题
- 邮件处理
- 日志模块
- pdf处理
- md5
- mongodb索引和聚合
- excel 读写
1. 发送邮件模块
这里指的邮件功能当然不是指的是职场上所谓的邮件,指的是程序运行中希望将程序运行的日志信息或者错误捕获信息发送给指定的收件人,通过邮件可以了解程序运行的状态或者出错信息。
关于邮件的基本概念,这里引用廖雪峰老师python教程中的邮件模块:
假设我们自己的电子邮件地址是me@163.com,对方的电子邮件地址是friend@sina.com(注意地址都是虚构的哈),现在我们用Outlook或者Foxmail之类的软件写好邮件,填上对方的Email地址,点“发送”,电子邮件就发出去了。这些电子邮件软件被称为MUA:Mail User Agent——邮件用户代理。
Email从MUA发出去,不是直接到达对方电脑,而是发到MTA:Mail Transfer Agent——邮件传输代理,就是那些Email服务提供商,比如网易、新浪等等。由于我们自己的电子邮件是163.com,所以,Email首先被投递到网易提供的MTA,再由网易的MTA发到对方服务商,也就是新浪的MTA。这个过程中间可能还会经过别的MTA,但是我们不关心具体路线,我们只关心速度。
Email到达新浪的MTA后,由于对方使用的是@sina.com的邮箱,因此,新浪的MTA会把Email投递到邮件的最终目的地MDA:Mail Delivery Agent——邮件投递代理。Email到达MDA后,就静静地躺在新浪的某个服务器上,存放在某个文件或特殊的数据库里,我们将这个长期保存邮件的地方称之为电子邮箱。
同普通邮件类似,Email不会直接到达对方的电脑,因为对方电脑不一定开机,开机也不一定联网。对方要取到邮件,必须通过MUA从MDA上把邮件取到自己的电脑上。
所以,一封电子邮件的旅程就是:
发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
发送邮件使用到两个模块:smtplib, email
发送邮件分为两步:
- email 构造邮件
- smtplib 协议发送邮件
邮件发送主要涉及SMTP协议, 接收主要涉及POP 协议、IMAP协议。
在学习之前:我们先看看一封邮件基本包含哪些基础的东西:这里以QQ邮件为例:
- 收件人
- 发件人
- 邮件主题
- 邮件正文
- 邮件附件
所以使用邮件模块的步骤大概也就是完成这些基本的构造:
这里是使用QQ邮箱发送给163企业邮箱的一个实例:
import email
from email.mime.text import MIMEText
import smtplib
from email.header import Header
# 构造邮件信息
msg = MIMEText('这是一份python自动脚本邮件信息。', 'plain', 'utf-8') # 邮件正文
# QQ发给公司: 多了这步 server.starttls()
from_addr = "xie_wei_sh@foxmail.com" # 发件人
password = "****" # 发件人邮箱密码
smtp_server = "smtp.qq.com" # SMTP 服务器地址
port = 587 # SMTP 服务器端口
to_addr = "paul.xie@chinascope.com" # 收件人
msg["From"] = Header("Python爱好者<{}>".format(from_addr)) # 显示的邮件发件人
msg["To"] = Header("admin<{}>".format(to_addr))
msg["Subject"] = Header("来自SMTP的问候", "utf-8") # 邮件主题
# 发送邮件
server = smtplib.SMTP(smtp_server, port)
server.starttls()
server.set_debuglevel(1)
server.login(from_addr, password=password)
server.sendmail(from_addr, [to_addr], msg=msg.as_string())
server.quit()
上文看上去比较复杂。有人问有没有更清晰的方法。更清晰的接口。
有的。
模块:yagmail 第三方模块,需要自己安装
import yagmail
# 设置发送人信息及SMTP服务器和端口
yag = yagmail.SMTP(
user="xie_wei_sh@foxmail.com",
password="****",
host="smtp.qq.com",
port="587"
)
contents = u"系统发出警告,你已严重违规。"
yag.send(to=["paul.xie@chinascope.com","1156143589@qq.com"], subject="yagmail", contents=contents)
# 参数说明
to : 收件人,可以接受一个list 发送至多人
subject: 邮件主题
contents: 邮件正文,默认是文本信息,其实还可以接收各种常见的文件比如,*.jpg, *.docx, *.pdf ,*.html 等信息,只需要设置完整路径,或者同一目录下文件名称即可。
- 总结
- 发送邮件的步骤: 构建邮件信息,SMTP协议发送邮件
- 更友好的第三方库yagmail
- 可能遇到的坑:SMTP协议服务器地址和端口不一致而产生的错误;再一个可能是邮箱设置中没有开启SMTP,POP等服务
2. 日志
借用python最佳实践中日志模块的介绍:
关于日志的作用:
诊断日志 记录与应用程序操作相关的日志。例如,用户遇到的报错信息,可通过搜索诊断日志获得上下文信息。
审计日志 为商业分析而记录的日志。从审计日志中,可提取用户的交易信息,并结合其他用户资料构成用户报告或者用来优化商业目标。
其实print
也能做到这些,那么为什么还使用日志模块呢?
一句话:日志更友好的了解程序运行中的信息或者错误信息,方便了解程序运行状态以及报错信息。
那么如何使用日志模块呢。logging
需要了解信息:
- 日志的级别
- 关于日志的基本概念:记录器,处理器,过滤器,格式化器
- 编写常规的日志需要的步骤
- 日志的级别:日志分等级,设置好等级,比设置好的级别大的才能在显示
- DEBUG
- INFO
- WARN
- ERROR
- CRITICAL
默认日志名为root
, 默认日志级别为WARN
在程序中配置日志存在三种方法:
- 使用INI文件配置
- 使用字典或者JSON配置
- 在程序源代码中配置
这里以在程序源代码中为例进行配置:读者要是感兴趣可以了解其他配置方式:
import logging
logger = logging.getLogger("logger_name") # 记录器
handler = logging.StreamHandler() # 日志显示在控制台,还可以设置将日志信息输出为文本形式FileHandler()
formatter = logging.Formatter(
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter) # 设置日志显示方式
logger.addHandler(handler) # 添加处理器
logger.setLevel(logging.DEBUG) # 设置日志级别
logger.debug('often makes a very good meal of %s', 'visiting tourists') # 日志在程序中的使用
- 总结
基本步骤: - 创建logger
logger = logging.getLogger("Your_logger_name")
logger.setLevel(logging.DEBUG)
- 创建handler
logger_one = logging.StreamHandler()
logger_one.setLevel(logging.INFO)
- 创建Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
- 配置Logger
logger_one.setFormatter(formatter)
logger.addHandler(logger_one)
- 使用logger
logger.info('info message')
3. pdf
存在这样一个需求:想要抓取网页上的信心,但发现所需要的信息在pdf中
在google中发现了其实存在将pdf信息转换为字符串信息的这种模块:pdfminer
# 读取本地pdf转化为字符串
from cStringIO import StringIO
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
def convert_pdf_2_text(path):
rsrcmgr = PDFResourceManager()
retstr = StringIO()
device = TextConverter(rsrcmgr, retstr, codec='utf-8', laparams=LAParams())
interpreter = PDFPageInterpreter(rsrcmgr, device)
with open(path, 'rb') as fp:
for page in PDFPage.get_pages(fp, set()):
interpreter.process_page(page)
text = retstr.getvalue()
device.close()
retstr.close()
return text
# 将数据读入内存在进行处理
url_data = requests.get("http://www.sse.com.cn/disclosure/bond/announcement/bookentry/c/2768799626348021.pdf").content
data = StringIO(url_data)
parser = PDFParser(data)
document = PDFDocument(parser)
rercmgr = PDFResourceManager(document)
retstr = StringIO()
laparams = LAParams()
codec = 'utf-8'
device = TextConverter(rercmgr, retstr, codec=codec, laparams=laparams)
interpreter = PDFPageInterpreter(rercmgr, device)
for page in PDFPage.create_pages(document):
interpreter.process_page(page)
text = retstr.getvalue()
# 返回的text 就能普通字符串的处理提取信息了。
4. md5
from hashlib
md = hashlib.md5()
md.update("字符串")
md5.hexdigest()
// 现在的问题是想要根据一个字典数据形成一个hash, 以使得可以判断字典数据没有完全一致的。
str = ("_".join("%s:%s" % (key, value) for key, value in dict_item.items() if key not in ("ct")) + "_" + str(number)).encode("utf-8")
md5.update(str)
md5.hexdigest()
5. 索引和聚合
遇到的问题是:能够很好的对数据进行判重处理。
之前的思路是:
- 根据数据字段进行数据唯一索引设置,这样处理其实去掉了好多为0的值,因为抓取的好多数据都是0,为了需要数据的完备性,这些数据其实也需要
之后的思路是:
- 根据入库的数据进行生成md5值,将md5值设置为唯一索引值,完成了较好的去重操作
- 索引
coll_values.create_index([("md5_values", pymongo.ASCENDING)], unique=True)
- 聚合操作:对mongodb数据中数据进行一些统计工作
- match
- group
- limit
- sort
query = [
{
"$match": {"key": code, "year": year, "type": type}
},
{
"$group": {"_id": {"code": "$key", "year":"$year"}, "total": {"$sum":1}}
}
]
total = []
for code in ["ACH", "IKGH", "ALN", "AMCN", "ATAI", "AXN", "ATV", "BIDU", "BITA", "BORN", "BSPM"]:
for one in ["2011", '2012', '2013', '2014', "2015"]:
A = Count(code=code, year=one, type="Annual", factor="Income Statement")
result = A.aggregation()
total.append(list(result))
pprint(total)
# 根据股票信息统计年份的数据数
6. excel 读写
需求是:想要将mongodb数据库中的数据导入入excel中
使用到的模块是:xlwt
关于excel的一些基本概念:
- Workbook : 工作簿
- sheet: 工作表
- cell: 单元格
一个workbook 可以包含多个sheet, 一个sheet中包含更多的行列组成的表格。
编写程序的大概步骤也是:
- 实例化workbook
- 添加sheet
- 往单元格里面写入内容
- 保存文件
import xlwt
wb = xlwt.Workbook()
ws = wb.add_sheet('A Test Sheet')
ws.write(0, 0, 1234.56) # 第一行第一列写入123.56
ws.write(2, 0, 1) # 第三行第一列写入 1
ws.write(2, 1, 1) # 第三行第二列写入 1
ws.write(2, 2, xlwt.Formula("A3+B3")) # 第三行第三列是前面值之和
wb.save('example.xls') # 保存为文件名example.xls
所以:xlwt的基本使用就是往单元格中进行内容的写入。
// mongodb 实例
from pymongo import MongoClient
from xlwt import Workbook
from xlwt import easyxf # 可以定义字体,颜色等样式
class MongoToXls(object):
def __init__(self, collection, name):
self.work_book = collection
self.sheet_name = name
self.wb = Workbook()
self.ws = [self.wb.add_sheet(one) for one in self.sheet_name]
self.style = easyxf("align: vert centre, horiz center")
pass
def info(self, number):
self.contents = list(self.work_book[number].find())
self.headers = [key for key in self.contents[0].keys()]
rows = len(self.contents)
columns = len(self.headers)
return rows, columns
def write_header(self, number):
ws = self.ws[number]
_, columns = self.info(number)
for i in range(0, columns):
ws.write(0, i, self.headers[i], style=self.style)
def write_content(self, number):
ws = self.ws[number]
rows, columns = self.info(number)
if rows >= 65536:
rows = 65535
for j in range(1, rows + 1):
for k in range(0, columns):
ws.write(j, k, str(self.contents[j - 1][self.headers[k]]), style=self.style)
def save(self):
name = "_".join(self.sheet_name) +"2"+ str(".xls")
self.wb.save(str(name))
collection1 = MongoClient()["db5"]["base"]
collection2 = MongoClient()["db5"]["items"]
collection3 = MongoClient()["db5"]["values"]
collection = [collection1, collection2, collection3]
name = ["base", "items", "values"]
A = MongoToXls(collection=collection, name=name)
for number in range(len(collection)):
print A.info(number)
A.write_header(number)
A.write_content(number)
A.save()
总结:
- 根据数据获取行和列数
- 先写入header信息
- 再两重循环写入内容值