爬虫系列(四)–全站爬取
全站爬取需要的数据基于一个这样的假设:某网站的页面上存在该网站其他页面的连接,通过这些连接跳转的新的页面进行数据的爬取。在开始这个之前,要先明白栈和队列。本篇中介绍的是单线程的实现方式,大规模的爬取需要多线程,分布式爬取。
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日,下一篇可能还不存在,不过肯定会写的。