廣度優(yōu)先和深度優(yōu)先
關(guān)于廣度優(yōu)先和深度優(yōu)先设塔,首先,不管是廣度還是深度,都需要定義一個(gè)爬取的深度 crawl_deepth寺擂,深度優(yōu)先比較容易實(shí)現(xiàn) 顯示遞歸嘛 爬取的層次。
所謂廣度優(yōu)先 就是要把當(dāng)前頁的 link 全部爬取完畢再進(jìn)行下一深度的遍歷,這就要給一些隊(duì)列分類 一般分為待爬隊(duì)列, 已爬隊(duì)列, 新加隊(duì)列, pop隊(duì)列响委,首先要確保訪問每個(gè)深度頁是的待爬隊(duì)列已經(jīng)清空 才獲取下一頁的超鏈接,思路嘛 大概可以用 while 1 來實(shí)現(xiàn) 窖梁。
當(dāng)然也可以直接寫好 pop 方法 直接 pop 出來 到最后清空隊(duì)列 繼續(xù)往下走赘风。
這里有一個(gè)簡(jiǎn)單的例子 可供參考,雖然沒有解析函數(shù) 思想可行纵刘。
定義 Redis 隊(duì)列
class RedisQueue(object):
def __init__(self, name, namespace='queue', **redis_kwargs):
self.__db = redis.Redis(host='127.0.0.1', port=6379, db=0, password=None)
self.key = f"{name,namespace}"
# 返回隊(duì)列大小
def qsize(self):
return self.__db.llen(self.key)
# 判斷隊(duì)列用盡
def empty(self):
return self.qsize() == 0
# rpush進(jìn)去或者lpush都可以
def put(self, item):
self.__db.rpush(self.key, item)
# get出來
def get(self, block=True, timeout=None)
if block:
item = self.__db.blpop(self.key, timeout=timeout)
else:
item = self.__db.lpop(self.key)
return item
def get_nowait(self):
return self.get(False)
廣度優(yōu)先
直接上套餐
class MyCrawler:
def __init__(self):
# 初始化當(dāng)前抓取的深度
self.current_deepth = 1
# 使用種子初始化url隊(duì)列
self.vistedQueue = RedisQueue("vistedQueue")
self.unvistedQueue = RedisQueue("unvistedQueue")
def put_unvistedQueue(self, seeds):
if isinstance(seeds, str):
self.unvistedQueue.put(seeds)
if isinstance(seeds, list):
for seed in seeds:
self.unvistedQueue.put(seed)
print("成功添加到未爬隊(duì)列")
# 主函數(shù)
def crawling(self, crawl_deepth):
# 深度 crawl_deepth
while self.current_deepth <= crawl_deepth:
# 確保清空隊(duì)列之后再繼續(xù) 先廣后深
while not self.unvistedQueue.empty():
# 出隊(duì)列
visitUrl = self.unvistedQueue.get_nowait().decode()
print(f"取出url {visitUrl}")
# 獲取超鏈接
links = self.getHyperLinks(visitUrl)
print(f"頁面 link 數(shù)量 {len(links)}")
# 將url放入已訪問的url中
self.vistedQueue.put(visitUrl)
print("當(dāng)前深度: " + str(self.current_deepth))
# 未訪問的url入列
for link in links:
self.unvistedQueue.put(link)
# 深度加 1
self.current_deepth += 1
# 獲取源碼中超鏈接
def getHyperLinks(self, url):
links = []
data = self.getPageSource(url)
if data:
soup = BeautifulSoup(data)
a = soup.findAll("a", {"href": re.compile('^http|^/')})
for i in a:
if i["href"].find("http://") != -1:
links.append(i["href"])
return links
# get html
def getPageSource(self, url, headers=None, timeout=15):
try:
request = requests.get(url,headers,timeout=timeout)
if request.status_code in [200,201]:
request.encoding = request.apparent_encoding
return request.text
except ConnectionError:
return None
執(zhí)行主函數(shù)
if __name__=="__main__":
# 指定爬取的深度 10
c = MyCrawler()
c.put_unvistedQueue(["http://www.baidu.com", "http://www.google.com"])
c.crawling(10)
我這里用的是 Redis 作為未爬 和 已爬的隊(duì)列(其實(shí)用內(nèi)置 set 也一樣)
由于我這里是 rpush(右邊進(jìn)) 和 lpop(左邊出) 這樣就達(dá)到了 先廣后深 的爬取目的了
深度優(yōu)先
比廣度優(yōu)先好寫一點(diǎn) 就是遞歸爬取 只需要定義深度
import requests
import re
import time
exist_urls = []
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36',
}
def get_link(url):
try:
response = requests.get(url=url, headers=headers)
response.encoding = 'UTF-8'
html = response.text
link_lists = re.findall('.*?<a target=_blank href="/item/([^:#=<>]*?)".*?</a>', html)
return link_lists
except Exception as e:
pass
finally:
exist_urls.append(url)
# 當(dāng)爬取深度小于10層時(shí)邀窃,遞歸調(diào)用主函數(shù),繼續(xù)爬取第二層的所有鏈接
def main(start_url, depth=1):
link_lists = get_link(start_url)
if link_lists:
unique_lists = list(set(link_lists) - set(exist_urls))
for unique_url in unique_lists:
unique_url = 'https://baike.baidu.com/item/' + unique_url
output = 'Depth:' + str(depth) + '\t' + start_url + '======>' + unique_url + '\n'
print(output)
with open('url.txt', 'a+') as f:
f.write(unique_url + '\n')
f.close()
if depth < 10:
main(unique_url, depth + 1)
執(zhí)行主函數(shù)
if __name__ == '__main__':
t1 = time.time()
start_url = 'https://baike.baidu.com/item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91'
main(start_url)
t2 = time.time()
print('總時(shí)間', t2 - t1)
以上的例子實(shí)現(xiàn) 深度優(yōu)先 和 廣度優(yōu)先的代碼假哎,其實(shí)沒啥復(fù)雜的瞬捕,也比較好理解。
提供思路舵抹,僅供參考肪虎。
歡迎轉(zhuǎn)載,但要聲明出處掏父,不然我順著網(wǎng)線過去就是一拳笋轨。
個(gè)人技術(shù)博客:http://www.gzky.live