要求:
1. 爬取58同城轉(zhuǎn)轉(zhuǎn)二手交易平臺商品信息 http://bj.58.com/pbdn/0/
2. 爬取每一頁商品的所有商品鏈接旷痕,爬取前十頁
3. 爬取每一件商品的詳細信息:類目碳锈,標題,價格欺抗,區(qū)域
分析:
1. 每一頁商品的鏈接格式都相同均為 :http://bj.58.com/pbdn/0/pn{}/售碳,
由此在{}內(nèi)填充1-10,即可得到前10頁的商品html頁面地址。
2.每一頁中每一條商品的詳情頁面均在
“div#infolist > div[class=infocon] > table.tbimg tbody > tr > td.t > a” 路徑下
3. 商品詳情頁:
類目:"div#nav > div.breadCrumb.f12 span a"
標題:"h1.info_titile"
價格:"span.price_now i"
區(qū)域:"div.palce_li span > i"
4. 避免程序的中斷绞呈,再次重啟后數(shù)據(jù)的重復(fù)下載贸人,構(gòu)造了一個緩存模塊:
1.設(shè)置緩存保質(zhì)期為1天,過期則刪除緩存
2. 對未在緩存內(nèi)的頁面進行下載佃声。
3.將頁面url作為唯一標示艺智,保存在緩存內(nèi)
4.將url轉(zhuǎn)換為MD5碼保存在磁盤中,
因為windows磁盤文件命名系統(tǒng)對特殊的字符有限制圾亏,而且最長不能超過255個字符十拣,
相對于url來說,可能包含一些非法的文件命名字符志鹃,所以轉(zhuǎn)換為MD5來存儲夭问,簡單粗暴
5. 將爬取的商品信息保存到CSV文件內(nèi)。如下:
具體代碼如下:
#coding=utf-8
"""爬取五八同城 轉(zhuǎn)轉(zhuǎn)二手交易平臺商品信息"""
import os
import csv
import time
import shutil
import datetime
import hashlib
import requests
from bs4 import BeautifulSoup
class Cache(object):
"""緩存類曹铃,用于處理緩存文件
緩存保質(zhì)期為1天缰趋,失效則刪除緩存文件"""
def __init__(self,cache_name='cache'):
"""如果緩存文件夾不存在,則新建緩存文件夾"""
if not os.path.exists(cache_name):
os.mkdir(cache_name)
#建立一個time文件,用來儲存上次啟動爬蟲的時間
#本次啟動時間與上次啟動時間的間隔大于1天埠胖,則清除所有緩存
with open(cache_name+'/time','w') as f:
#獲得當(dāng)前系統(tǒng)時間糠溜,并按照時間格式轉(zhuǎn)換為字符串格式
time_str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
f.write(time_str)
with open(cache_name + '/time', 'r') as f:
time_str = f.read()
time_date = datetime.datetime.strptime(time_str,'%Y-%m-%d %H:%M:%S')
if datetime.datetime.now() - time_date > datetime.timedelta(days=1):
#shutil是一個高級的文件操作模塊
#rmtree 刪除目錄下的所有文件及文件夾,包括子文件夾內(nèi)的所有文件及文件夾直撤,遞歸刪除
#os模塊
#os.remove(name)只能刪除文件非竿,不能刪除文件夾(目錄)
#os.rmdir(name)只能刪除空文件夾(目錄),非空則報錯
#os.removedirs()刪除空文件夾(目錄)及空的子文件夾(目錄),非空則報錯谋竖。遞歸刪除
shutil.rmtree(cache_name)
print '緩存保存日期大于1天红柱,已過期,從新下載數(shù)據(jù)'
else:
print '緩存正常 非過期'
self.cache_name = cache_name
def __url_md5(self,url):
"""獲得url對應(yīng)的md5碼"""
# 獲得url的MD5碼
# 因為windows磁盤文件命名系統(tǒng)對特殊的字符有限制蓖乘,而且最長不能超過255個字符
# 相對于url來說锤悄,可能包含一些非法的文件命名字符,所以轉(zhuǎn)換為MD5來存儲嘉抒,簡單粗暴
md5 = hashlib.md5()
md5.update(url)
return md5.hexdigest()
def is_cache(self,url):
"""判斷緩存是否存在
返回 True 表示存在零聚,F(xiàn)alse表示不存在"""
cache_list = os.listdir(self.cache_name) # 獲得緩存文件名列表
# 獲得url的MD5碼
md5 = self.__url_md5(url)
if md5 in cache_list:
print '已經(jīng)緩存該 {} 網(wǎng)頁'.format(url)
return True
return False
def save_cache(self,url):
"""保存url到緩存"""
old_path = os.getcwd() # 獲得當(dāng)前工作目錄路徑
os.chdir(self.cache_name) # 改變工作目錄為緩存文件
#print '切換后--',os.getcwd()
with open(self.__url_md5(url), 'w'):
pass
print '緩存不存在,緩存 {} 網(wǎng)頁'.format(url)
os.chdir(old_path) # 切換到原始目錄
def get_html(url):
"""獲得網(wǎng)頁源碼"""
time.sleep(1) # 延遲1秒
headers = {
'User-Agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
'Chrome/54.0.2840.87 Safari/537.36')
}
html = requests.get(url, headers=headers).text
return html
def get_product_list(url):
"""獲得當(dāng)前頁所有商品的鏈接
返回一個list"""
html = get_html(url)
soup = BeautifulSoup(html, 'lxml')
# div[class=infocom] 表示選擇 <div class = 'infocom'>xxxx</div>的標簽
# <div class = 'infocom jzcon'>xxxx</div> 則不會被選擇
# 如果需要選擇 class='infocom jzcon'
# 可以使用 'div.infocom.jzcon'
# 切記:'div.infocom' 是只要 class屬性里包含 infocm 就會被查詢到
# 例如:<div class = 'infocom'>xxxx</div> <div class = 'infocom jzcon'>xxxx</div>
# 'div.infocom' 會返回查詢到的 兩個標簽信息 而不是一個
# 想要得到唯一的<div class = 'infocom'>些侍,
# 則需要 'div[class=infocom']
products = soup.select('div#infolist > div[class=infocon] > table.tbimg tbody > tr > td.t > a')
product_list = [product.get('href') for product in products]
print '當(dāng)前頁 {} 有 {} 個商品'.format(url,len(product_list))
return product_list
def product_detailed_info(url):
"""獲得商品的詳細信息
商品類目隶症,商品標題,商品價格岗宣,商品區(qū)域
"""
html = get_html(url)
soup = BeautifulSoup(html, 'lxml')
product_type = soup.select('div#nav > div.breadCrumb.f12 span a')[-1].get_text().strip()
product_title = soup.select('h1.info_titile')[0].get_text().strip()
product_price = soup.select('span.price_now i')[0].get_text().strip()
product_area = soup.select('div.palce_li span > i')[0].get_text()
product_dict = {}
product_dict['type'] = product_type.encode('utf-8')
product_dict['title'] = product_title.encode('utf-8')
product_dict['price'] = product_price
product_dict['area'] = product_area.encode('utf-8')
#爬取一個商品詳細信息蚂会,保存一個,避免程序的以外中斷導(dǎo)致數(shù)據(jù)的丟失
save_product_info(product_dict,'product.csv')
print '保存商品信息到文件 {}'.format(url)
def save_product_info(info,file_name):
"""保存商品信息到指定文件內(nèi)"""
fieles = ['price','area','type','title']
if not os.path.exists(file_name):
with open(file_name, 'wb') as f:
writer = csv.DictWriter(f, fieldnames=fieles)
writer.writerow(dict(zip(fieles, fieles)))
with open(file_name,'ab') as f:
writer = csv.DictWriter(f, fieldnames=fieles)
writer.writerow(info)
def start():
"""啟動爬蟲"""
# 獲得前十頁的url
base_url = 'http://bj.58.com/pbdn/0/pn{}/'
urls = [base_url.format(page) for page in range(1, 11)]
# 獲得每一頁中的商品鏈接
product_url_set = set()
for url in urls:
product_url_set.update(get_product_list(url))
print '總共 {} 商品'.format(len(product_url_set))
#獲得每件商品的詳細信息
#并將已下載過的url保存到緩存中
count = 0
cache = Cache() #緩存
for product_url in product_url_set:
if not cache.is_cache(product_url):
product_detailed_info(product_url)
cache.save_cache(product_url)
count += 1
print '本次保存 {} 件商品'.format(count)
if __name__ == '__main__':
#啟動爬蟲
start()