Heim > Artikel > Backend-Entwicklung > python编写网页爬虫脚本并实现APScheduler调度
爬虫爬的页面是京东的电子书网站页面,每天会更新一些免费的电子书,爬虫会把每天更新的免费的书名以第一时间通过邮件发给我,通知我去下载
前段时间自学了python,作为新手就想着自己写个东西能练习一下,了解到python编写爬虫脚本非常方便,且最近又学习了MongoDB相关的知识,万事具备只欠东风。
程序的需求是这样的,爬虫爬的页面是京东的电子书网站页面,每天会更新一些免费的电子书,爬虫会把每天更新的免费的书名以第一时间通过邮件发给我,通知我去下载。
一、编写思路:
1.爬虫脚本获取当日免费书籍信息
2.把获取到的书籍信息与数据库中的已有信息作比较,如果书籍存在不做任何操作,书籍不存在,执行插入数据库的操作,把数据的信息存入MongoDB
3.执行数据库插入操作时,把更新的数据以邮件的形式发送出来
4.用APScheduler调度框架完成python脚本调度
二、脚本的主要知识点:
1.python简单爬虫
本次用到的模块有urllib2用来抓取页面,导入模块如下:
import urllib2 from sgmllib import SGMLParser
urlopen()方法获取网页HTML源码,都存储在content中,listhref()类主要的功能是解析HTML代码,处理HTML类型的半结构化文档。
content = urllib2.urlopen('http://sale.jd.com/act/yufbrhZtjx6JTV.html').read() listhref = ListHref() listhref.feed(content)
listhref()类代码可以在下面全部代码中查询到,这里只说几个关键点:
listhref()类继承了SGMLParser 类并重写了其中的内部方法。SGMLParser 将HTML分解成有用的片段,比如开始标记和结束标记。一旦成功地分解出某个数据为一个有用的片段,它会根据所发现的数据,调用一个自身内部的方法。为了使用这个分析器,您需要子类化 SGMLParser类,并且重写父类的这些方法。
SGMLParser 将 HTML 分析成不同类数据及标记,然后对每一类调用单独的方法:
开始标记 (Start_tag)
是一个开始一个块的 HTML 标记,像 100db36a723c770d327fc0aef2ce13b1,93f0f5c25f18dab9d176bd4f6de5d30e,6c04bd5ca3fcae76e30b72ad730ca86d , e03b848252eb9375d56be284e690e873 等,或是一个独一的标记,象 0c6dc11e160d3b678d68754cc175188a 或 a1f02c36ba31691bcfe87b2722de723b 等。本例当它找到一个开始标记3499910bf9dac5ae3c52d5ede7383485,SGMLParser将查找名为 start_a或do_a的方法。如果找到了,SGMLParser会使用这个标记的属性列表来调用这个方法;否则,它用这个标记的名字和属性列表来调用unknown_starttag方法。
结束标记 (End_tag)
是结束一个块的HTML标记,像 73a6ac4ed44ffec12cee46588e518a5e,9c3bca370b5104690d9ef395f2c5f8d1,36cc49f0c466276486e50c850b7e4956 或 bc5574f69a0cba105bc93bd3dc13c4ec 等。本例中当找到一个结束标记时,SGMLParser 将查找名为end_a的方法。如果找到,SGMLParser调用这个方法,否则它使用标记的名字来调用unknown_endtag。
文本数据(Text data)
获取文本块,当不满足其它各类别的任何标记时,调用handle_data获取文本。
以下的几类在本文中没有用到
字符引用 (Character reference)
用字符的十进制或等同的十六进制来表示的转义字符,当找到该字符,SGMLParser用字符调用 handle_charref 。
实体引用 (Entity reference)
HTML实体,像&ref,当找到该实体,SGMLParser实体的名字调用handle_entityref。
注释 (Comment)
HTML注释, 包括在 a69de9cf48008bc5997724f3266c029c之间。当找到,SGMLParser用注释内容调用handle_comment。
处理指令 (Processing instruction)
HTML处理指令,包括在 85ea88108c1a188f76629eb8510cc975 之间。当找到,SGMLParser用指令内容调 handle_pi。
声明 (Declaration)
HTML声明,如DOCTYPE,包括在 42c1a4c041bf732a08e2183b951c1e5b之间。当找到,SGMLParser用声明内容调用handle_decl。
具体的说明参考API:http://www.php.cn/
2.python操作MongoDB数据库
首先要安装python对mongoDB的驱动PyMongo,下载地址:http://www.php.cn/
导入模块
import pymongo
连接数据库服务器127.0.0.1和切换到所用数据库mydatabase
mongoCon=pymongo.Connection(host="127.0.0.1",port=27017) db= mongoCon.mydatabase
查找数据库相关书籍信息,book为查找的collection
bookInfo = db.book.find_one({"href":bookItem.href})
为数据库插入书籍信息,python支持中文,但是对于中文的编码和解码还是比较复杂,相关解码和编码请参考http://www.php.cn/
b={ "bookname":bookItem.bookname.decode('gbk').encode('utf8'), "href":bookItem.href, "date":bookItem.date } db.book.insert(b,safe=True)
关于PyMongo请参考API文档http://www.php.cn/
3.python发送邮件
导入邮件模块
# Import smtplib for the actual sending function import smtplib from email.mime.text import MIMEText
"localhost"为邮件服务器地址
msg = MIMEText(context) #文本邮件的内容
msg['Subject'] = sub #主题
msg['From'] = "my@vmail.cn" #发信人
msg['To'] = COMMASPACE.join(mailto_list) #收信人列表
def send_mail(mailto_list, sub, context): COMMASPACE = ',' mail_host = "localhost" me = "my@vmail.cn" # Create a text/plain message msg = MIMEText(context) msg['Subject'] = sub msg['From'] = "my@vmail.cn" msg['To'] = COMMASPACE.join(mailto_list) send_smtp = smtplib.SMTP(mail_host) send_smtp.sendmail(me, mailto_list, msg.as_string()) send_smtp.close()
应用文档:http://www.php.cn/
4.Python调度框架ApScheduler
下载地址http://www.php.cn/
官方文档:http://www.php.cn/
API:http://www.php.cn/
安装方法:下载之后解压缩,然后执行python setup.py install,导入模块
from apscheduler.scheduler import Scheduler
ApScheduler配置比较简单,本例中只用到了add_interval_job方法,在每间隔一段时间后执行任务脚本,本例中的间隔是30分钟。可参考实例文章http://www.php.cn/
# Start the scheduler sched = Scheduler() sched.daemonic = False sched.add_interval_job(job,minutes=30) sched.start()
关于daemonic参数:
apscheduler会创建一个线程,这个线程默认是daemon=True,也就是默认的是线程守护的。
在上面的代码里面,要是不加上sched.daemonic=False的话,这个脚本就不会按时间运行。
因为脚本要是没有sched.daemonic=False,它会创建一个守护线程。这个过程中,会创建scheduler的实例。但是由于脚本运行速度很快,主线程mainthread会马上结束,而此时定时任务的线程还没来得及执行,就跟随主线程结束而结束了。(守护线程和主线程之间的关系决定的)。要让脚本运行正常,必须设置该脚本为非守护线程。sched.daemonic=False
附:全部脚本代码
All Code
#-*- coding: UTF-8 -*- import urllib2 from sgmllib import SGMLParser import pymongo import time # Import smtplib for the actual sending function import smtplib from email.mime.text import MIMEText from apscheduler.scheduler import Scheduler #get freebook hrefs class ListHref(SGMLParser): def __init__(self): SGMLParser.__init__(self) self.is_a = "" self.name = [] self.freehref="" self.hrefs=[] def start_a(self, attrs): self.is_a = 1 href = [v for k, v in attrs if k == "href"] self.freehref=href[0] def end_a(self): self.is_a = "" def handle_data(self, text): if self.is_a == 1 and text.decode('utf8').encode('gbk')=="限时免费": self.hrefs.append(self.freehref) #get freebook Info class FreeBook(SGMLParser): def __init__(self): SGMLParser.__init__(self) self.is_title="" self.name = "" def start_title(self, attrs): self.is_title = 1 def end_title(self): self.is_title = "" def handle_data(self, text): if self.is_title == 1: self.name=text #Mongo Store Module class freeBookMod: def __init__(self, date, bookname ,href): self.date=date self.bookname=bookname self.href=href def get_book(bookList): content = urllib2.urlopen('http://sale.jd.com/act/yufbrhZtjx6JTV.html').read() listhref = ListHref() listhref.feed(content) for href in listhref.hrefs: content = urllib2.urlopen(str(href)).read() listbook=FreeBook() listbook.feed(content) name = listbook.name n= name.index('》') #print (name[0:n+2]) freebook=freeBookMod(time.strftime('%Y-%m-%d',time.localtime(time.time())),name[0:n+2],href) bookList.append(freebook) return bookList def record_book(bookList,context,isSendMail): # DataBase Operation mongoCon=pymongo.Connection(host="127.0.0.1",port=27017) db= mongoCon.mydatabase for bookItem in bookList: bookInfo = db.book.find_one({"href":bookItem.href}) if not bookInfo: b={ "bookname":bookItem.bookname.decode('gbk').encode('utf8'), "href":bookItem.href, "date":bookItem.date } db.book.insert(b,safe=True) isSendMail=True context=context+bookItem.bookname.decode('gbk').encode('utf8')+',' return context,isSendMail #Send Message def send_mail(mailto_list, sub, context): COMMASPACE = ',' mail_host = "localhost" me = "my@vmail.cn" # Create a text/plain message msg = MIMEText(context) msg['Subject'] = sub msg['From'] = "my@vmail.cn" msg['To'] = COMMASPACE.join(mailto_list) send_smtp = smtplib.SMTP(mail_host) send_smtp.sendmail(me, mailto_list, msg.as_string()) send_smtp.close() #Main job for scheduler def job(): bookList=[] isSendMail=False; context="Today free books are" mailto_list=["mailto@mail.cn"] bookList=get_book(bookList) context,isSendMail=record_book(bookList,context,isSendMail) if isSendMail==True: send_mail(mailto_list,"Free Book is Update",context) if __name__=="__main__": # Start the scheduler sched = Scheduler() sched.daemonic = False sched.add_interval_job(job,minutes=30) sched.start()