欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-08 23:20:39
...

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

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


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日,下一篇可能还不存在,不过肯定会写的。