zoukankan      html  css  js  c++  java
  • 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 标记,像 <html>,<head>,<body> , <pre> 等,或是一个独一的标记,象 <br> 或 <img> 等。本例当它找到一个开始标记<a>,SGMLParser将查找名为 start_a或do_a的方法。如果找到了,SGMLParser会使用这个标记的属性列表来调用这个方法;否则,它用这个标记的名字和属性列表来调用unknown_starttag方法。
    结束标记 (End_tag)
    是结束一个块的HTML标记,像 </html>,</head>,</body> 或 </pre> 等。本例中当找到一个结束标记时,SGMLParser 将查找名为end_a的方法。如果找到,SGMLParser调用这个方法,否则它使用标记的名字来调用unknown_endtag。
    文本数据(Text data)
    获取文本块,当不满足其它各类别的任何标记时,调用handle_data获取文本。

    以下的几类在本文中没有用到
    字符引用 (Character reference)
    用字符的十进制或等同的十六进制来表示的转义字符,当找到该字符,SGMLParser用字符调用 handle_charref 。
    实体引用 (Entity reference)
    HTML实体,像&ref,当找到该实体,SGMLParser实体的名字调用handle_entityref。
    注释 (Comment)
    HTML注释, 包括在 <!-- ... -->之间。当找到,SGMLParser用注释内容调用handle_comment。
    处理指令 (Processing instruction)
    HTML处理指令,包括在 <? ... > 之间。当找到,SGMLParser用指令内容调 handle_pi。
    声明 (Declaration)
    HTML声明,如DOCTYPE,包括在 <! ... >之间。当找到,SGMLParser用声明内容调用handle_decl。

    具体的说明参考API:http://docs.python.org/2/library/sgmllib.html?highlight=sgmlparser#sgmllib.SGMLParser 

    2.python操作MongoDB数据库

    首先要安装python对mongoDB的驱动PyMongo,下载地址:https://pypi.python.org/pypi/pymongo/2.5

    导入模块

    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://blog.csdn.net/mayflowers/article/details/1568852

    b={
                   "bookname":bookItem.bookname.decode('gbk').encode('utf8'),
                   "href":bookItem.href,
                   "date":bookItem.date
                   }
                db.book.insert(b,safe=True)

    关于PyMongo请参考API文档http://api.mongodb.org/python/2.0.1/

    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://docs.python.org/2/library/email.html?highlight=smtplib#

    4.Python调度框架ApScheduler

    下载地址https://pypi.python.org/pypi/APScheduler/2.1.0

    官方文档:http://pythonhosted.org/APScheduler/#faq

    API:http://pythonhosted.org/APScheduler/genindex.html

    安装方法:下载之后解压缩,然后执行python setup.py install,导入模块

    from apscheduler.scheduler import Scheduler

    ApScheduler配置比较简单,本例中只用到了add_interval_job方法,在每间隔一段时间后执行任务脚本,本例中的间隔是30分钟。可参考实例文章http://flykite.blog.51cto.com/4721239/832036

    # 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()
  • 相关阅读:
    Java连接MySql报错—— com.mysql.cj.exceptions.InvalidConnectionAttributeException
    Java——XML基础知识
    Java——多线程基础知识
    Java——线程安全的集合
    Java——集合
    dom4j——使用dom4j生成xml
    Java——用程序编译一个文件夹下所有java文件到另一个文件夹下
    Java——DOS命令窗口用命令编译文件夹下所有.java文件
    Java——删除Map集合中key-value值
    python 枚举Enum
  • 原文地址:https://www.cnblogs.com/wly923/p/3015732.html
Copyright © 2011-2022 走看看