掌握了BeatifulSoup的基本用法之后奠涌,爬取單個網(wǎng)頁實(shí)際上是比較簡單的:只需要使用requests庫中的get方法先向網(wǎng)頁發(fā)出請求职辨,用BeatifulSoup把網(wǎng)頁轉(zhuǎn)成soup抬闷,再對soup使用各種select方法铭污,即可得到所需的網(wǎng)頁元素葛作,再稍做整理腕柜,即可得到所需的結(jié)構(gòu)化內(nèi)容济似。
那么,如果要爬取一系列的網(wǎng)頁內(nèi)容呢盏缤?
這就需要對爬取過程做一下調(diào)度準(zhǔn)備了砰蠢,下面以趕集網(wǎng)二手物品信息為例,介紹一下10萬量級網(wǎng)頁的爬取過程唉铜。
0.爬取目標(biāo)
選擇一個地市的趕集全部二手物品類目作為爬取目標(biāo)台舱,爬取其下所有二手物品頁面、二手物品詳細(xì)信息潭流。
1.制定爬取過程策略
我們按照倒序來分析一下爬取過程
-最后一步:爬取最終的爬取頁面竞惋,也就是上面的圖二(爬取目標(biāo)),存儲所需信息
-倒數(shù)第二步:通過類目頁面獲取最后一步所需的最終頁面的鏈接
-倒數(shù)第三步:想辦法獲取類目頁面鏈接<-- 通過趕集首頁-二手欄目灰嫉,下方的二手物品類目拆宛,如上面圖一。
我們再把這個過程正過來讼撒,也就是正常在趕集上找到對應(yīng)物品信息的過程——爬取過程就是把這個找尋的過程重復(fù)再重復(fù)浑厚。
當(dāng)然,上述處理過程中根盒,需要將中間爬取的鏈接一步一步存儲下來钳幅、一步一步再提取出來使用,并規(guī)避重復(fù)炎滞。
2.動手開始爬敢艰!
過程明確了,實(shí)現(xiàn)起來就不復(fù)雜了册赛。主要需要注意的是中間存url钠导、取url的過程:注意避免重復(fù)——爬過的留一個已爬記錄,爬前到這個記錄里檢查一下有沒有击奶,如果已爬辈双,那就跳過!
(1)第一部分代碼:獲取類目信息
import requests
from bs4 import BeautifulSoup
headers = {
'user-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36'
}
home_url = 'http://xa.ganji.com/wu/'
wb_data = requests.get(home_url,headers = headers)
soup = BeautifulSoup(wb_data.text,'lxml')
#print(soup)
utag = soup.select('dl > dt > a[target="_blank"]')
url_list = ''
for item in utag:
utag_tag = item.get('href')
#print(utag_tag)
url = 'http://xa.ganji.com{}'.format(utag_tag)
url_list = url_list + url + '\n'
print(url)
獲取完后柜砾,把類目url存儲為變量channel_list,備用。
(2)第二部分代碼:兩個獲取頁面信息的函數(shù)
這一步我們先寫獲取最終頁面鏈接的函數(shù)换衬、再寫通過最終鏈接獲取目標(biāo)信息對應(yīng)的函數(shù)痰驱。
函數(shù)1的參數(shù)有類目鏈接证芭、子頁面編碼兩個,在調(diào)用這個函數(shù)的時(shí)候担映,我們再去寫對應(yīng)的編碼循環(huán)废士、類目循環(huán)。
函數(shù)2的參數(shù)只有最終頁面鏈接一個蝇完。
import pymongo
import requests
from bs4 import BeautifulSoup
import time
from get_ori_url import channel_list,headers
client = pymongo.MongoClient('localhost',27017)
ganji = client['ganji']
#重新建立一個url集
urlset = ganji['urlset']
urlspideset = ganji['urlspideset']
second_info = ganji['second_info']
#step1: get the urls of secondhand
def get_urls(channel,pages):
url_secondhands = '{}o{}'.format(channel,pages)
time.sleep(6)
print(url_secondhands)
db_urls = [item['url'] for item in channel_sec.find()]
if url_secondhands in db_urls:
print('the',url_secondhands,'has spide already!')
pass
else:
wb_data = requests.get(url_secondhands,headers = headers)
soup = BeautifulSoup(wb_data.text,'lxml')
#add the url into have spide
channel_sec.insert_one({'url':url_secondhands})
#add: if page error,then pass
url_secondhand = soup.select('dd.feature > div > ul > li > a')
for item in url_secondhand:
url_s = item.get('href')
urlset.insert_one({'url':url_s})
print(url_s)
# insert the url had spide into a set;
#step2: get the information we need from the secondhand pages
def get_item_info(url):
time.sleep(2)
db_urls = [item['url'] for item in urlspideset.find()]
if url in db_urls:
print('the url "',url,'"has aready spide!')
pass
else:
wb_data = requests.get(url,headers=headers)
soup = BeautifulSoup(wb_data.text,'lxml')
title = soup.select('h1')
pagetime = soup.select('div.col-cont.title-box > div > ul.title-info-l.clearfix > li:nth-of-type(1) > i')
type = soup.select('div.leftBox > div:nth-of-type(3) > div > ul > li:nth-of-type(1) > span > a')
price = soup.select('div > ul > li:nth-of-type(2) > i.f22.fc-orange.f-type')
address = soup.select('div.leftBox > div:nth-of-type(3) > div > ul > li:nth-of-type(3)')
# can't get the pv,need other method
# pageview = soup.select('pageviews')
for t1,p1,type1,price1,add1 in zip(title,pagetime,type,price,address):
data = {
'title':t1.get_text(),
'pagetime':(p1.get_text().replace('發(fā)布','')).strip(),
'type':type1.get_text(),
'price':price1.get_text(),
'address':list(add1.stripped_strings)
}
second_info.insert_one(data)
print(data)
urlspideset.insert_one({'url':url})
(3)第三部分代碼:調(diào)用上述函數(shù)獲取目標(biāo)信息
首先是調(diào)用函數(shù)get_urls官硝,通過類目信息,獲取最終頁面鏈接集:
from multiprocessing import Pool
from get_ori_url import channel_list
from spider_ganji import get_urls
def get_url_from_channel(channel):
for num in range(1,71):
get_urls(channel,num)
if __name__ == '__main__':
pool = Pool()
pool.map(get_url_from_channel,channel_list.split())
根據(jù)調(diào)用的函數(shù)短蜕,獲取的鏈接會存儲在MongoDB下的ganji庫urlset表中氢架。
再調(diào)用函數(shù) get_item_info,逐個頁面獲取所需信息:
from multiprocessing import Pool
from spider_ganji import get_item_info
from spider_ganji import urlset
from spider_ganji import urlspideset
#get all urls need to spide:
db_urls = [item['url'] for item in urlset.find()]
#get the urls already spide:
url_has_spide = [item['url'] for item in urlspideset.find()]
x = set(db_urls)
y = set(url_has_spide)
rest_urls = x-y
if __name__ == '__main__':
pool = Pool()
pool.map(get_item_info,rest_urls)
這一步用了一點(diǎn)去除已爬頁面的技巧:首先是爬的過程中將已爬取url記錄下來(也可以存儲在所爬信息庫中)朋魔,如果出現(xiàn)中斷岖研,用所有需爬取 剔除 已爬取,即可規(guī)避重復(fù)爬取問題警检。
(關(guān)于剔重孙援、規(guī)避中斷過程中出現(xiàn)的問題,應(yīng)該還有更好的解決方案:先記錄異常頁面扇雕、跳過拓售、繼續(xù)爬,最后再處理異常頁面應(yīng)該更合適镶奉?)
3.總結(jié)
大道至簡邻辉,問題的解決方案應(yīng)該是簡潔的,大規(guī)模的數(shù)據(jù)爬取也是一樣:難點(diǎn)并不在于某幾個頁面怎么爬腮鞍,而在于過程上的控制和調(diào)度:調(diào)度過程越清晰值骇,實(shí)現(xiàn)起來越容易,實(shí)現(xiàn)過程中多翻翻文檔就好了(雖然我也覺得文檔看起來累移国,不過確實(shí)還得翻吱瘩,就跟認(rèn)字時(shí)翻字典一樣——現(xiàn)在只需要翻“電子詞典”已經(jīng)很方便了!)