`

Python写的简易采集器

阅读更多

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

# 简易采集爬虫
#    1.采集Yahoo!Answers,parseData函数修改一下,可以采集任何网站
#    2.需要sqlite3或者pysqlite支持
#    3.可以在DreamHost.com空间上面运行
#    4.可以修改User-Agent冒充搜索引擎蜘蛛
#    5.可以设置暂停的时间,控制采集速度
#    6.采集Yahoo会被封IP数小时,所以这个采集用处不大
# Author: Lukin<mylukin@gmail.com>
# Date  : 2008-09-25

# 导入采集需要用到的模块
import re, sys, time
import httplib, os.path as osp
from urlparse import urlparse
# 使用sqite数据库,为了兼容DreamHost.com的空间,只能这么写了
try :
    import sqlite3 as sqlite
except ImportError:
    from pysqlite2 import dbapi2 as sqlite

# 采集速度控制,单位秒
sleep = 0
# 数据库路径
dbname = './database.db'
# 设置提交的header头
headers = {"Accept": "*/*","Referer": "http://answers.yahoo.com/","User-Agent": "Mozilla/5.0+(compatible;+Googlebot/2.1;++http://www.google.com/bot.html)"}
# 连接服务器
dl = httplib.HTTPConnection('answers.yahoo.com')
# 连接数据库
conn = sqlite.connect(osp.abspath(dbname))

# 创建数据库
def createDatabase():
    global conn,dbname;
    if osp.isfile(osp.abspath(dbname)) : return
    c = conn.cursor()
    # 创建url列表存放表
    c.execute('''CREATE TABLE IF NOT EXISTS [collect]([cid] INTEGER PRIMARY KEY,[curl] TEXT,[state] INTEGER DEFAULT '0',UNIQUE([curl]));''')
    c.execute('''CREATE INDEX IF NOT EXISTS [collect_idx_state] ON [collect]([state]);''')
    # 创建分类表
    c.execute('''CREATE TABLE IF NOT EXISTS [sorts]([sortid] INTEGER PRIMARY KEY,[sortname] TEXT,[sortpath] TEXT,[sortfoot] INTEGER DEFAULT '0',[sortnum] INTEGER DEFAULT '0',UNIQUE([sortpath]));''')
    c.execute('''CREATE INDEX IF NOT EXISTS [sorts_idx_sortname] ON [sorts]([sortname]);''')
    c.execute('''CREATE INDEX IF NOT EXISTS [sorts_idx_sortfoot] ON [sorts]([sortfoot]);''')
    # 创建文章表
    c.execute('''CREATE TABLE IF NOT EXISTS [article]([aid] INTEGER PRIMARY KEY,[sortid] INTEGER DEFAULT '0',[hits] INTEGER DEFAULT '0',[title] TEXT,[path] TEXT,[question] TEXT,[banswer] TEXT,[oanswer] TEXT,UNIQUE([path]));''')
    c.execute('''CREATE INDEX IF NOT EXISTS [article_idx_sortid] ON [article]([sortid]);''')
    # 事物提交
    conn.commit()
    c.close()

# 执行采集
def collect(url="http://answers.yahoo.com/"):
    global dl,error,headers; R = 0
    print "GET:",url
    urls = urlparse(url); path = urls[2];
    if urls[4]!='' : path += '?' + urls[4]
    dl.request(method="GET", url=path, headers=headers); rs = dl.getresponse()
    if rs.status==200 :
        R = parseData(rs.read(),url);
    else :
        print "3 seconds, try again ..."; time.sleep(3)
        dl.request(method="GET", url=path, headers=headers); rs = dl.getresponse()
        if rs.status==200 :
            R = parseData(rs.read(),url);
        else :
            print "3 seconds, try again ..."; time.sleep(3)
            dl.request(method="GET", url=path, headers=headers); rs = dl.getresponse()
            if rs.status==200 :
                R = parseData(rs.read(),url);
            else :
                print "Continue to collect ..."
                R = 3
    # 更新记录
    updateOneUrl(url,R)
    # 返回结果
    return R

# 处理采集到的数据
def parseData(html,url):
    global dl,conn; R = 2;
    c = conn.cursor()
    # 格式化html代码
    format = formatURL(clearBlank(html),url)
    # 取出所有的连接
    urls = re.findall(r'''(<a[^>]*?href="([^"]+)"[^>]*?>)|(<a[^>]*?href='([^']+)'[^>]*?>)''',format,re.I)
    if urls != None :
        i = 0
        # 循环所有的连接
        for regs in urls :
            # 得到一个单一的url
            sUrl = en2chr(regs[1].strip())
            # 判断url是否符合规则,符合,则插入数据库
            if re.search('http(.*?)/(dir|question)/index(.*?)',sUrl,re.I) != None :
                if re.search('http(.*?)/dir/index(.*?)',sUrl,re.I) != None:
                    if sUrl.find('link=list') == -1 and sUrl.find('link=over') == -1 :
                        sUrl+= '&link=over'
                    else:
                        sUrl = sUrl.replace('link=list','link=over')
                if sUrl[-11:]=='link=mailto' : continue
                try :
                    c.execute('INSERT INTO [collect]([curl])VALUES(?);',(sUrl,))
                    i = i + 1
                except sqlite.IntegrityError :
                    pass
        if i>0 : print "Message: %d get a new URL." % (i,)
    # 截取数据
    if re.search('http(.*)/question/index(.*)',url,re.I) != None :
        sortfoot = 0
        # 自动创建分类和分类关系
        guide  = sect(format,'<ol id="yan-breadcrumbs">','</ol>','(<li>(.*?)Home(.*?)</li>)')
        aGuide = re.findall('<a[^>]*href="[^"]*"[^>]*>(.*?)</a>',guide,re.I)
        if aGuide != None :
            sortname = ""
            for sortname in aGuide :
                sortname = sortname.strip()
                sortpath = en2path(sortname)
                # 查询分类是否存在
                c.execute('SELECT [sortid],[sortname] FROM [sorts] WHERE [sortpath]=? LIMIT 0,1;',(sortpath,))
                row = c.fetchone();
                # 分类不存在,添加分类
                if row==None :
                    c.execute('INSERT INTO [sorts]([sortname],[sortpath],[sortfoot])VALUES(?,?,?);',(sortname,sortpath,sortfoot))
                    sortfoot = c.lastrowid
                else:
                    sortfoot = row[0]
            # 标题
            title = sect(format,'<h1 class="subject">','</h1>')
            # 最佳答案
            BestAnswer = sect(format,'(<h2><span>Best Answer</span>(.*?)</h2>(.*?)<div class="content">)','(</div>)')
            # 最佳答案不存在,则不采集
            if BestAnswer != None :
                # 文章路径
                path = en2path(sortname + '-' + title.strip())
                # 问题
                adddata = sect(format,'<div class="additional-details">','</div>')
                content = sect(format,'(<h1 class="subject">(.*?)<div class="content">)','(</div>)')
                if adddata != None : content += '<br/>' + adddata
                # 其他回答
                OtherAnswer = ''
                for regs in re.findall('<div class="qa-container">(.+?)<div class="utils-container">',format):
                    if regs.find('<h2>') == -1 and regs.find('</h2>') == -1 :
                        a1 = sect(regs,'<div class="content">','</div>')
                        a2 = sect(regs,'<div class="reference">','</div>')
                        OtherAnswer+= '<div class="oAnswer">' + a1
                        if a2 != None : OtherAnswer+= '<div class="reference">' + a2 + '</div>'
                        OtherAnswer+= '</div>'
                # 判断采集成功
                if title != None and content != None :
                    # 将数据写入到数据
                    try :
                        c.execute('INSERT INTO [article]([sortid],[title],[path],[question],[banswer],[oanswer])VALUES(?,?,?,?,?,?);',(sortfoot,title,path,content,BestAnswer,OtherAnswer))
                        print "Message:%s.html" % (path,)
                        R = 1
                    except sqlite.IntegrityError :
                        pass
    # 提交写入数据库
    conn.commit(); c.close()
    return R
# 取得一条URL
def getOneUrl():
    global conn; c = conn.cursor()
    c.execute('SELECT [curl] FROM [collect] WHERE [state] IN(0,3) LIMIT 0,1;')
    row = c.fetchone(); c.close()
    if row==None : return ""
    return row[0].encode('utf-8')

# 更新一条记录的状态
def updateOneUrl(url,state):
    global conn; c = conn.cursor()
    c.execute('UPDATE [collect] SET [state]=? WHERE [curl]=?;',(state,url))
    conn.commit(); c.close()

# 清除html代码里的多余空格
def clearBlank(html):
    if len(html) == 0 : return ''
    html = re.sub('\r|\n|\t','',html)
    while html.find("  ")!=-1 or html.find('&nbsp;')!=-1 :
        html = html.replace('&nbsp;',' ').replace('  ',' ')
    return html

# 格式化url
def formatURL(html,url):
    urls = re.findall('''(<a[^>]*?href="([^"]+)"[^>]*?>)|(<a[^>]*?href='([^']+)'[^>]*?>)''',html,re.I)
    if urls == None : return html
    for regs in urls :
        html = html.replace(regs[0],matchURL(regs[0],url))
    return html

# 格式化单个url
def matchURL(tag,url):
    urls = re.findall('''(.*)(src|href)=(.+?)( |/>|>).*|(.*)url\(([^\)]+)\)''',tag,re.I)
    if urls == None :
        return tag
    else :
        if urls[0][5] == '' :
            urlQuote = urls[0][2]
        else:
            urlQuote = urls[0][5]

    if len(urlQuote) > 0 :
        cUrl = re.sub('''['"]''','',urlQuote)
    else :
        return tag

    urls = urlparse(url); scheme = urls[0];
    if scheme!='' : scheme+='://'
    host = urls[1]; host = scheme + host
    if len(host)==0 : return tag
    path = osp.dirname(urls[2]);
    if path=='/' : path = '';
    if cUrl.find("#")!=-1 : cUrl = cUrl[:cUrl.find("#")]
    # 判断类型
    if re.search('''^(http|https|ftp):(//|\\\\)(([\w/\\\+\-~`@:%])+\.)+([\w/\\\.\=\?\+\-~`@':!%#]|(&amp;)|&)+''',cUrl,re.I) != None :
        # http开头的url类型要跳过
        return tag
    elif cUrl[:1] == '/' :
        # 绝对路径
        cUrl = host + cUrl
    elif cUrl[:3]=='../' :
        # 相对路径
        while cUrl[:3]=='../' :
            cUrl = cUrl[3:]
            if len(path) > 0 :
                path = osp.dirname(path)
    elif cUrl[:2]=='./' :
        cUrl = host + path + cUrl[1:]
    elif cUrl.lower()[:7]=='mailto:' or cUrl.lower()[:11]=='javascript:' :
        return tag
    else :
        cUrl = host + path + '/' + cUrl
    R = tag.replace(urlQuote,'"' + cUrl + '"')
    return R

# html代码截取函数
def sect(html,start,end,cls=''):
    if len(html)==0 : return ;
    # 正则表达式截取
    if start[:1]==chr(40) and start[-1:]==chr(41) and end[:1]==chr(40) and end[-1:]==chr(41) :
        reHTML = re.search(start + '(.*?)' + end,html,re.I)
        if reHTML == None : return
        reHTML = reHTML.group()
        intStart = re.search(start,reHTML,re.I).end()
        intEnd = re.search(end,reHTML,re.I).start()
        R = reHTML[intStart:intEnd]
    # 字符串截取
    else :
        # 取得开始字符串的位置
        intStart = html.lower().find(start.lower())
        # 如果搜索不到开始字符串,则直接返回空
        if intStart == -1 : return
        # 取得结束字符串的位置
        intEnd = html[intStart+len(start):].lower().find(end.lower())
        # 如果搜索不到结束字符串,也返回为空
        if intEnd == -1 : return
        # 开始和结束字符串都有了,可以开始截取了
        R = html[intStart+len(start):intStart+intEnd+len(start)]
    # 清理内容
    if cls != '' :
        R = clear(R,cls)
    # 返回截取的字符
    return R

# 正则清除
def clear(html,regexs):
    if regexs == '' : return html
    for regex in regexs.split(chr(10)):
        regex = regex.strip()
        if regex != '' :
            if regex[:1]==chr(40) and regex[-1:]==chr(41):
                html = re.sub(regex,'',html,re.I|re.S)
            else :
                html = html.replace(regex,'')
    return html

# 格式化为路径
def en2path(enStr):
    return re.sub('[\W]+','-',en2chr(enStr),re.I|re.U).strip('-')

# 替换实体为正常字符
def en2chr(enStr):
    return enStr.replace('&amp;','&')

# ------------------------------------- 开始执行程序 -------------------------------------------

# 首先创建数据库
createDatabase()

# 开始采集
loops = 0
while True:
    if loops>0 :
        url = getOneUrl()
        if url == "" :
            loops = 0
        else :
            loops = collect(url)
    else :
        loops = collect()
    # 暂停
    time.sleep(sleep)
    if loops==0 : break
# 关闭HTTP连接
dl.close()
# 退出程序
sys.exit()

 

本文是引用 http://www.cnblogs.com/kuyuecs/archive/2008/10/15/1311346.html

分享到:
评论

相关推荐

    基于python的简易物联网系统设计实现

    在本文中,我们将深入探讨如何使用Python编程语言设计和实现一个简易的物联网(IoT)系统。Python因其简洁明了的语法和丰富的库支持而成为物联网应用开发的热门选择。以下是一些关键知识点: 1. **Python编程基础**...

    基于python的简易物联网系统的设计与实现.docx

    【基于Python的简易物联网系统设计与实现】 摘要: 本文主要探讨了如何利用Python语言设计和实现一个简化的物联网系统。物联网(IoT)是当前信息技术领域的重要发展方向,它通过网络连接物理世界的各种设备,实现数据...

    第五届蓝桥杯: 简易温度采集与控制装置

    4. **编程语言**:C/C++通常用于编写微控制器的程序,Python等可能用于上位机的数据处理与显示。 5. **实时操作系统(RTOS)**:对于复杂的控制任务,可能需要使用RTOS来管理多任务并发执行。 6. **控制算法**:...

    DS18B20和51单片机构成的测温设备程序以及对应的简易Python上位机.zip

    “简易Python上位机”则指的是一个使用Python编程语言开发的上位机软件。在这样的系统中,上位机通常运行在个人电脑上,通过串行通信(如UART或USB转串口)与51单片机连接,接收并显示从下位机(51单片机)传来的...

    shiboqi.zip_12864波_示波器_简易示波器

    【标题】"shiboqi.zip_12864波_示波器_简易示波器" 提供的信息表明,这个压缩包内包含了一个与示波器相关的项目,特别是使用了12864显示屏的设计。12864通常指的是128x64像素的LCD(液晶显示器),这是一种常见的...

    4.简易温度采集与控制14_coatiu1_温度采集与控制、蓝桥杯_neededr5p_

    简易温度采集与控制14_coatiu1_温度采集与控制、蓝桥杯_neededr5p_”表明这是一个关于温度采集与控制系统的学习资源,可能是一份项目或者教程,涉及到蓝桥杯比赛的相关内容。"coatiu1"可能是该项目或课程的一个特定...

    2020年Python爬虫全套课程100节【2021-03-09】.docx

    - **案例分析**:第7节介绍了使用Requests库实现简易网页采集器的具体步骤,包括指定URL、发起请求、获取响应数据以及持久化存储等。 - **反爬技术破解**:第8节通过对百度翻译页面的抓包分析,展示了如何绕过简单的...

    基于野火指南者开发板的简易示波器,使用emwin+freertos,用片上adc采样,最高1.2MHz.zip

    包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】...

    简易风洞及控制系统(内含丰富的代码和原理图)

    3. **代码实现**:在控制系统中,可能使用到的编程语言有C/C++、Python或MATLAB等。代码主要用于数据采集、信号处理、控制算法的实现。例如,PID(比例-积分-微分)控制器是常见的风洞速度控制算法,通过实时调整...

    《商务数据采集与处理》(微课版)教学大纲.docx

    2. 数据采集工具的介绍,如爬虫软件(如Python爬虫)的应用,以及数据采集器的使用,包括简易和自定义采集模式,高级功能如增量采集、智能防封和登录采集等。 3. 数据采集器的定位技术,如XPath的运用,以及云采集...

    第五届蓝桥杯之温度采集与控制

    3. **数据采集与处理**:采集到的温度数据需要通过A/D转换器转化为数字信号,然后在嵌入式系统中进行处理。这可能包括数据滤波、平均值计算等,以提高测量精度和稳定性。 4. **实时操作系统(RTOS)**:如果系统...

    2020电赛-简易温度测量.7z

    OpenMV是一个开源的微控制器视觉库,它允许开发者使用Python语言进行机器视觉编程。在本项目中,OpenMV主要负责图像处理和分析任务,如人脸识别和口罩检测。通过其内置的摄像头捕获图像,然后应用算法识别面部特征,...

    Thermometer:用于实时显示LM35温度传感器的简短Arduino-Python组合

    标题中的“Thermometer:用于实时显示LM35温度传感器的简短Arduino-Python组合”表明这是一个项目,它结合了Arduino微控制器和Python编程语言来实时监控和展示LM35温度传感器的数据。这个项目旨在创建一个简易的温度...

    毕设&课程作业_基于stm32的简易示波器.zip

    【标题】:“基于STM32的简易示波器”是一个以STM32微控制器为核心的电子设计项目,旨在实现一个简单的示波器功能。在电子工程领域,示波器是一种常用的测试设备,用于观察电信号的变化,是理解和调试电路的重要工具...

    简易旋转倒立摆及控制装置 电子设计

    在电子设计领域,简易旋转倒立摆及其控制装置是一个典型的控制系统设计案例,它结合了机械、电子和控制理论,展示了微控制器(如AVR单片机)在动态系统中的应用。这个项目的主要目标是使倒立摆能够在一定的角度范围...

    2019年电赛K题_简易多功能液体容器.rar

    3. **数据采集与处理**:通过微控制器读取传感器数据,进行初步的信号调理和数据处理。 4. **实时操作系统(RTOS)**:如果系统复杂度较高,可能需要使用RTOS来管理多任务执行。 5. **电路设计**:包括电源电路、...

    基于单片机的简易逻辑分析仪资料.7z

    在单片机实现的简易逻辑分析仪中,核心是数据采集和处理部分,通常由ADC(模拟数字转换器)和FIFO(先进先出存储器)组成。ADC负责将模拟信号转化为数字信号,FIFO则用于暂存这些数字信号,以供后续处理。 在单片机...

    29.2013年简易旋转倒立摆.zip

    简易旋转倒立摆可能包含了电机、传感器(如角度传感器、陀螺仪)、微控制器等硬件组件。电机负责提供必要的扭矩以保持平衡,传感器则用于实时监测倒立摆的状态,微控制器则接收传感器数据并计算出控制信号,驱动电机...

Global site tag (gtag.js) - Google Analytics