您的位置:首页 > 编程语言 > Python开发

【详解】Python爬虫脚本M9优化

2015-11-20 19:34 866 查看
本例中,在原来爬虫脚本的基础上添加了新的需求。

需要生成一个新的csv文件,写入M9充值方式的结果。

其实在原有的基础上改的话,很简单,因为逻辑很清晰,只需要在查询的时候加一个查询条件,

分析得知,条件是pcid=26。

queryUrl2 = "http://money.downjoy.com/connectchannel/list_prompt_new_channel_stat.html?startDate=%s&endDate=%s&pcid=26"%(lastDayDateStr,todayStr )

知道了要使用这个链接,我们甚至可以直接只改这里,然后跑一遍脚本就能够得到我们所需要的csv文件,并且自动发送邮件。

改个附件名就可以了。

但是,我们的脚本是需要在脚本后台自动跑的,所以我们需要做的就是在一个脚本中,

生成两个文件,并把这两个文件添加到邮件的附件,发送。

这样,优化原有的Python脚本就很必要了。

因为,两个文件的写入逻辑是非常非常一样的,只是查询链接不同而已。

一样,意味着代码会重复,代码重复意味着代码可重用,代码可重用的意思就是,可以抽象出方法来。

我们需要花很大精力要做的,即是抽象出方法来。

一者,使得逻辑更加清晰;

二者,方便以后维护,如果以后再添加一个文件,改起来就很方便了。

【问题】

在抽象方法的过程中。

我们遇到了这样几个问题

【全局变量的使用】

today = datetime.datetime.today()
todayStr = datetime.datetime.strftime(today, "%Y-%m-%d")
lastDayDate = today - datetime.timedelta(1)
lastDayDateStr = datetime.datetime.strftime(lastDayDate, "%Y-%m-%d")

xlsfile = r'read.xlsx'
fileName='downjoyBackendData_'+lastDayDateStr+'_.csv'
fileNameM9='downjoyBackendData_'+lastDayDateStr+'_M9.csv'

mainUrl="http://money.downjoy.com/connectchannel/login.jsp"
loginUrl="http://money.downjoy.com/connectchannel/login.html"
queryUrl1 = "http://money.downjoy.com/connectchannel/list_prompt_new_channel_stat.html?startDate=%s&endDate=%s"%(lastDayDateStr,todayStr )
queryUrl2 = "http://money.downjoy.com/connectchannel/list_prompt_new_channel_stat.html?startDate=%s&endDate=%s&pcid=26"%(lastDayDateStr,todayStr )
logoutUrl ="http://money.downjoy.com/connectchannel/logout.html"

csvfile1=file(fileName, 'ab+')
writer1 = csv.writer(csvfile1, dialect='excel')
csvfile2=file(fileNameM9, 'ab+')
writer2 = csv.writer(csvfile2, dialect='excel')

book = xlrd.open_workbook(xlsfile)
sheet = book.sheet_by_index(0)
nrows = sheet.nrows

以上,是我们在改进这个爬虫脚本时在最顶端定义的全局变量。

我们知道,Python以及其他我们所知的编程语言中,参数的使用,

一者,是定义变量,然后调用,全局变量的好处是,定义了然后可以在任意地方使用;

二者,是传递参数,主要是用到了函数或者方法的时候,我们需要传参。

这里我们采用全局变量的定义这种方式!

因为很多地方都用到了,传递太过麻烦,直接调用反而简便。

而且我们需要知道一点,Python脚本运行的时候,最先执行的代码其实不是main方法,而是顶端的全局变量代码。

如果我们的全局变量定义有问题,最一开始就会有提示!

【文件的写入模式】

至今为止,我们写入csv经常使用的方式是ab+,即追加模式,防止我们再次或多次写入的时候,覆盖原先的内容。

但是,我们之前不清楚的是,这里的覆盖指的是多次写入才会覆盖。

我们之前对多次写入的概念不太清楚!

多次写入覆盖发生,是因为多次open和close了。即,

open---写入---close---open---写入----close!

如果这个时候我们采用w的写入,当然只有最后一次的写入内容了。

但是本例的改进,我们做了一件事,即,将open这样的操作放到了全局变量中,

def main():
print "===%s start===%s"%(sys.argv[0], datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S"))
writeHeader(writer1)
writeHeader(writer2)
toLogin()
toWrite()
csvfile1.close()
csvfile2.close()
sendEmail()
print "===%s end===%s"%(sys.argv[0], datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S"))


我们一开始运行脚本的时候,就是open的状态,只有最终我们执行完整个脚本,才会close。

这也是全局变量的一个好处!

所以这里我们采用覆盖写入的方式!即w写入!

为什么要这么写呢?

因为如果一天之内跑多次脚本的话,写出来的表就会是两张表的结合。而我们需要的表,只需要一份数据而已。

所以我们这里改为w模式写入。

【w模式写入出现空行的问题】

出现空行,我们的解决方法是将w改为wb。

b是什么意思呢?这里作一下解释!

Python中csv的writer打开文件的时候要小心,要通过binary模式去打开,即带b的,比如

wb,ab+

指明了是binary模式后,就不会出现空行了!

【Python中的异常处理】

我们在测试这个脚本的时候遇到过一个问题,报了http的错误。

即,运行脚本的时候网络出了问题,导致脚本运行中断!

这属于异常的一种,我们要对其进行捕获处理。如果该脚本部署到脚本后台,运行的时候遇到网络问题,就会异常中断。

会给我们的正常工作带来很大的困扰。

所以我们将遍历登录部分的代码修改如下:

def toWrite():

# 遍历登录并登出
for i in range(1, nrows):
id = getId(i)
username = getUserName(i)
password = getPassword(i)
try:
login(id, password)

data1=getData(queryUrl1)
data2=getData(queryUrl2)

writeContent(id,username, password,data1,writer1)
writeContent(id,username,password,data2,writer2)

# 退出登录
urllib2.urlopen(logoutUrl)
except Exception, e:
print str(e)

即,对所有可能会出现的异常捕获并打印错误信息!

最后我们贴一下最终版的代码,方便以后查看!

主要是我们再看代码的时候,要注意这些方法的抽象!

如果我们以后还改,有了抽象出来的方法,就能很方便的添加新的文件写入之类的。

万一以后又有个需求说要将易联充值也写出一个csv文件来,就方便很多了,我们只需要在各个关键部分添加一行一行的代码就能搞定了。

不需要动关键逻辑部分的代码!只需要添加!

#!/usr/bin/python
# -*- coding: utf-8 -*-

__author__ = "$Author: wangxin.xie$"
__version__ = "$Revision: 1.1 $"
__date__ = "$Date: 2015-11-19 16:55$"

###############################################################
# 功能:自动抓取当乐网与渠道合作后台数据
###############################################################

import re
import cookielib
import urllib
import urllib2
import datetime
import xlrd
import csv
import math
import smtplib
import sys
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from bs4 import BeautifulSoup

#####################全局变量###########################################

today = datetime.datetime.today()
todayStr = datetime.datetime.strftime(today, "%Y-%m-%d")
lastDayDate = today - datetime.timedelta(1)
lastDayDateStr = datetime.datetime.strftime(lastDayDate, "%Y-%m-%d")

xlsfile = r'read.xlsx'
fileName='downjoyBackendData_'+lastDayDateStr+'_.csv'
fileNameM9='downjoyBackendData_'+lastDayDateStr+'_M9.csv'

mainUrl="http://money.downjoy.com/connectchannel/login.jsp"
loginUrl="http://money.downjoy.com/connectchannel/login.html"
queryUrl1 = "http://money.downjoy.com/connectchannel/list_prompt_new_channel_stat.html?startDate=%s&endDate=%s"%(lastDayDateStr,todayStr )
queryUrl2 = "http://money.downjoy.com/connectchannel/list_prompt_new_channel_stat.html?startDate=%s&endDate=%s&pcid=26"%(lastDayDateStr,todayStr )
logoutUrl ="http://money.downjoy.com/connectchannel/logout.html"

csvfile1=file(fileName, 'wb')
writer1 = csv.writer(csvfile1, dialect='excel')
csvfile2=file(fileNameM9, 'ab+')
writer2 = csv.writer(csvfile2, dialect='excel')

book = xlrd.open_workbook(xlsfile)
sheet = book.sheet_by_index(0)
nrows = sheet.nrows

#####################模拟登录##############################################
def writeCsv(x,writer):
# 写入csv
writer.writerow(x)

# 打印表头
def writeHeader(writer):
writeCsv(["账号".decode('utf-8').encode('gbk'),
"用户名".decode('utf-8').encode('gbk'),
"密码".decode('utf-8').encode('gbk'),
"应用名称".decode('utf-8').encode('gbk'),
"日期".decode('utf-8').encode('gbk'),
"充值金额".decode('utf-8').encode('gbk'),
"用户付费次数".decode('utf-8').encode('gbk')],writer)

def toLogin():
# 进入登录页面
urllib2.urlopen(mainUrl)
# 处理cookie
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)

def getId(i):
id=int(math.floor(sheet.cell_value(i, 0)))
return id

def getUserName(i):
username=sheet.cell_value(i, 1)
return username

def getPassword(i):
password=sheet.cell_value(i, 2)
return password

def login(id,password):
# 登录
postDict = {
'channelId' :id,
'password' : password,
}
postData = urllib.urlencode(postDict)
req = urllib2.Request(loginUrl, postData)
urllib2.urlopen(req)

# 删除多余的tag标签
def delTag(x):
arr = []
for i in range(0, len(x)):
b = str(x[i]).replace('<td>', '')
c = b.replace('</td>', '')
arr.append(c)
return arr

def getData(url):
resp=urllib2.urlopen(url)
html = resp.read().decode('utf-8').encode('gbk')
soup = BeautifulSoup(html, "html.parser", from_encoding="gbk")

# data表示td标签的所有内容
data = soup.find_all(name="td", attrs={}, text=re.compile("\S"))
data = delTag(data)
return data

def writeData(id, username, password, data, writer):

# 抓取数据的数组
gameName=[None]*(len(data)/4)
date=[None]*(len(data)/4)
account=[None]*(len(data)/4)
times=[None]*(len(data)/4)

for i in range(len(gameName)):
gameName[i]=data[i*4+1].decode('utf-8').encode('gbk')

for i in range(len(date)):
date[i]=data[i*4+2].decode('utf-8').encode('gbk')

for i in range(len(account)):
account[i]=data[i*4+3].decode('utf-8').encode('gbk')

for i in range(len(times)):
times[i]=data[(i+1)*4].decode('utf-8').encode('gbk')

# 实现按列写入
rowNum = len(gameName)
columnNum = 7
chartData = [None]*rowNum
for i in range(0, len(chartData)):
chartData[i] = [None]*columnNum
for i in range(0, len(gameName)):
chartData[i] = [id, username, password, gameName[i], date[i], account[i], times[i]]
writeCsv(chartData[i],writer)

def writeContent(id,username,password,data,writer):

# 若提取的用户名密码错误
if len(data)==2:
writeCsv([id, username, "password_error", "error", "please", "update", "password"],writer)
return

# 若查询数据为空
if len(data)==3:
writeCsv([id, username, password, 0, lastDayDateStr, 0, 0],writer)
return

writeData(id, username, password, data, writer)

def toWrite(): # 遍历登录并登出 for i in range(1, nrows): id = getId(i) username = getUserName(i) password = getPassword(i) try: login(id, password) data1=getData(queryUrl1) data2=getData(queryUrl2) writeContent(id,username, password,data1,writer1) writeContent(id,username,password,data2,writer2) # 退出登录 urllib2.urlopen(logoutUrl) except Exception, e: print str(e)

# 发送邮件
def sendEmail():

# 创建一个带附件的实例
msg = MIMEMultipart()

# 构造附件1
att1 = MIMEText(open('downjoyBackendData_'+lastDayDateStr+'_.csv', 'rb').read(), 'base64', 'gb2312')
att1["Content-Type"] = 'application/octet-stream'

# fileName以数据加日期命名
fileName="数据".decode('utf-8').encode('gbk')+todayStr+".csv"
att1["Content-Disposition"] = 'attachment; filename=%s'%fileName
msg.attach(att1)

# 构造附件2
att2 = MIMEText(open(fileNameM9, 'rb').read(), 'base64', 'gb2312')
att2["Content-Type"] = 'application/octet-stream'

# fileName以数据加日期命名
fileName="M9数据".decode('utf-8').encode('gbk')+todayStr+".csv"
att2["Content-Disposition"] = 'attachment; filename=%s'%fileName
msg.attach(att2)

# 写入邮件正文
text="数据见附件,如有问题,请联系ZC@chuan-mei.com"
# 邮件正文乱码,所以在这里指定编码
part1 = MIMEText(text, 'plain', _charset='utf-8')
msg.attach(part1)

# 加邮件头
#strTo = ['shijia.ren@chuan-mei.com', 'ybb@chuan-mei.com']
strTo = ['wangxin.xie@chuan-mei.com', '1520481162@qq.com']
msg['to'] = ','.join(strTo)
msg["from"] = 'datacentre@chuan-mei.com'
msg['subject'] = '当乐商人每日分发数据_'.decode('utf-8')+todayStr

# 发送邮件
try:
server = smtplib.SMTP()

# 数据中心测试时
server.connect('mail.chuan-mei.com')
# 用户名,密码
server.login('datacentre@chuan-mei.com', 'DtCtre@CM-19')
server.sendmail(msg['from'], strTo, msg.as_string())
server.quit()
print '发送成功'.decode("utf-8")
except Exception, e:
print str(e)

def main(): print "===%s start===%s"%(sys.argv[0], datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S")) writeHeader(writer1) writeHeader(writer2) toLogin() toWrite() csvfile1.close() csvfile2.close() sendEmail() print "===%s end===%s"%(sys.argv[0], datetime.datetime.strftime(datetime.datetime.now(), "%Y-%m-%d %H:%M:%S"))
#################################################################################
if __name__ == "__main__":
main()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: