爬虫系列(四)–全站爬取


爬虫系列(四)–全站爬取

全站爬取需要的数据基于一个这样的假设:某网站的页面上存在该网站其他页面的连接,通过这些连接跳转的新的页面进行数据的爬取。在开始这个之前,要先明白栈和队列。本篇中介绍的是单线程的实现方式,大规模的爬取需要多线程,分布式爬取。


1.实现步骤

(1)准备几个起始链接加入待队列Q中,例如Q=[“http://www.xxx.com/aaa/”,”http://www.xxx.com/bbb/”,”http://www.xxx.com/ccc/”]

(2)并将这几个链接加入一个入队集合S中,S={“http://www.xxx.com/aaa/”,”http://www.xxx.com/bbb/”,”http://www.xxx.com/ccc/”}这个集合作用是保证一个网页只爬取一次。

(3)从Q中取出一个链接url(出队,取出队首元素,并从队列中删除该元素),如果Q中没有链接,结束爬取。如果有链接url=”http://www.xxx.com/aaa/”,进行(4)步骤。

(4)对url爬取,保存需要的数据(写到文件中,建议使用json格式保存,一行一个页面),找出该页面上的所有链接urls

(5)把符合我们要求的连接(例如以http://www.xxx.com 开头的链接)找出来,判断每一个连接urli是否在S中。如果不在S中,把urli加入S,urli入队

(6)继续执行(3)步骤

注意1:这个是广度优先爬取,如果把队换成栈,会变成深度优先爬取。如果没有特殊的需求,一般都是使用广度优先爬取。

注意2:对于一个小网站来说,这样操作没有什么问题,但是有些网站页面很多,Q和S中存储的连接太多直接撑爆内存,这时可以实现一个硬盘队列(栈)和硬盘集合,本系列文章不实现这些功能。

注意3:有些网站的连接到站内的url形如”/aaa?a=1&b=2″,需要改写成”http://域名/aaa?a=1&b=2″

注意4:有些网站会根据短时间内一个ip访问大量页面制定反爬虫策略,可以爬取一个页面后,休眠一段时间接着爬取


2.代码实现(代码仅对于代码中要爬取的网站有效,其他网站需要重新配置规则)

import time
import os
import json
from urllib import request
from lxml import etree

header_dict = {
    "Accept":"application/json, text/javascript, */*; q=0.01",
    "Accept-Language":"zh-CN,zh;q=0.9",
    "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
    }
def get_http(load_url,header=None):
    res=""
    try:
        req = request.Request(url=load_url,headers=header)#创建请求对象
        coonect = request.urlopen(req)#打开该请求
        byte_res = coonect.read()#读取所有数据,很暴力
        try:
            res=byte_res.decode(encoding='utf-8')
        except:
            try:
                res=byte_res.decode(encoding='gbk')
            except:
                res=""
    except Exception as e:
        print(e)
    return res

#创建数据文件夹
if not os.path.exists("./sina_data"):
    os.mkdir("sina_data")
#输出的文件
raw_file=open("./sina_data/raw.txt","w",encoding="utf-8")
json_file=open("./sina_data/json.txt","w",encoding="utf-8")

#数据存储模板
obj={"title":"","url":"","content":""}

saved_url={"http://www.sina.com.cn/"}#记录已经爬取过的页面
url_list=["http://www.sina.com.cn/"]#url队列

while len(url_list)>0:
    url=url_list.pop()
    #获取html
    html_text=get_http(url,header_dict)
    if html_text=="":
        continue
    time.sleep(0.15)
    try:
        tree=etree.HTML(html_text)
        dir(tree)
        #url有这样特征的可能是文章
        if url.find("html")>=0 and url.find("list")<0:
            #找出文章标题,根据多个页面找出标题规则,发现不满足该规则的标题要添加进去
            t_xpath=[
                "//h1[@id='main_title']/text()",
                "//h1/text()",
                "//th[@class='f24']//font/text()",
                "/html/head/title/text()"
                ]
            title=[]
            for tx in t_xpath:
                if len(title)==0:
                    title=tree.xpath(tx)
                else:
                    break
            
            #找出文章正文,根据多个页面找出正文规则,发现不满足该规则的正文要添加进去
            c_xpath=[
                "//div[@id='artibody']//p/text()",
                "//td[@class='l17']//p/text()",
                "//div[@class='content']//p/text()",
                "//div[@class='article']//p/text()",
                "//div[@id='article']//p/text()",
                "//div[@class='article-body main-body']//p/text()",
                "//div[@class='textbox']//p/text()"
                ]
            content=[]
            for cx in c_xpath:
                if len(content)==0:
                    content=tree.xpath(cx)
                else:
                    break
            
            if len(title)*len(content)==0:
                #没有标题或正文保存原始网页,这个可以不写,有些页面根本不是文章
                raw_file.write(html_text.replace("\n", "").replace("\r", ""))
                raw_file.write("\n")
                print("没有标题或正文"+url)
            else:
                #既有标题,也有正文保存数据
                obj["url"]=url
                obj["title"]=title[0]
                obj["content"]=" ".join(content)
                jstr=json.dumps(obj)
                json_file.write(jstr)
                json_file.write("\n")
                
        #找到所有url
        urls=tree.xpath("//a/@href")
        for u in urls:
            
            flag=False
            
            #过滤url
            end_filter=[".apk",".iso",".jpg",".jpeg",".bmp",".cdr",".php",".exe",".dmg",".apk"]
            for f in end_filter:
                if "".endswith(f):
                    flag=True
                    break
            if flag:
                continue
            
            find_filter=[
                "vip.","guba.","lottery.","kaoshi.","club.baby","jiancai.",".cn/ku/","astro.","match.","games.","zhongce","list",
                "photo.","yangfanbook","zx.jiaju","nc.shtml","english.","download","chexian","auto","video","comfinanceweb.shtml",
                "//sax.","login","/bc.","aipai.","vip.book","talk.t","slide.","club.baby","biz.finance","blog","comment5","www.leju",
                "http://m."
                ]
            for f in find_filter:
                if u.find(f)>=0:
                    flag=True
                    break
            if flag:
                continue
              
            if u.startswith("http") and u.find(".sina.")>=0:
                if u in saved_url:
                    continue
                #加入待爬取队列
                saved_url.add(url)
                url_list.append(u)
    except Exception as e:
        print("error")
raw_file.close()
json_file.close()
        
        

下一篇文章,使用爬虫爬取某商城网站评论数据。这类数据是在页面内动态加载的,之前这种方式已经不适用了。除了评论外,下拉加载的页面也是这样的。想了解这类数据爬取方法,那就快看下一篇吧。今天是2018年9月17日,下一篇可能还不存在,不过肯定会写的。