Python 爬蟲實戰計劃:第一周實戰作業

要求:
1. 爬取58同城轉轉二手交易平臺商品信息 http://bj.58.com/pbdn/0/
2. 爬取每一頁商品的所有商品鏈接,爬取前十頁
3. 爬取每一件商品的詳細信息:類目,標題,價格,區域
分析:
1. 每一頁商品的鏈接格式都相同均為 :http://bj.58.com/pbdn/0/pn{}/,
由此在{}內填充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"
區域:"div.palce_li span > i"
4. 避免程序的中斷,再次重啟后數據的重復下載,構造了一個緩存模塊:
1.設置緩存保質期為1天,過期則刪除緩存
2. 對未在緩存內的頁面進行下載。
3.將頁面url作為唯一標示,保存在緩存內
4.將url轉換為MD5碼保存在磁盤中,
因為windows磁盤文件命名系統對特殊的字符有限制,而且最長不能超過255個字符,
相對于url來說,可能包含一些非法的文件命名字符,所以轉換為MD5來存儲,簡單粗暴
5. 將爬取的商品信息保存到CSV文件內。如下:

image.png

具體代碼如下:

#coding=utf-8
"""爬取五八同城 轉轉二手交易平臺商品信息"""
import os
import csv
import time
import shutil
import datetime
import hashlib
import requests
from bs4 import BeautifulSoup

class Cache(object):
    """緩存類,用于處理緩存文件
        緩存保質期為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:
                #獲得當前系統時間,并按照時間格式轉換為字符串格式
                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 刪除目錄下的所有文件及文件夾,包括子文件夾內的所有文件及文件夾,遞歸刪除
                #os模塊
                #os.remove(name)只能刪除文件,不能刪除文件夾(目錄)
                #os.rmdir(name)只能刪除空文件夾(目錄),非空則報錯
                #os.removedirs()刪除空文件夾(目錄)及空的子文件夾(目錄),非空則報錯。遞歸刪除
                shutil.rmtree(cache_name)
                print '緩存保存日期大于1天,已過期,從新下載數據'
            else:
                print '緩存正常 非過期'
        self.cache_name = cache_name

    def __url_md5(self,url):
        """獲得url對應的md5碼"""
        # 獲得url的MD5碼
        # 因為windows磁盤文件命名系統對特殊的字符有限制,而且最長不能超過255個字符
        # 相對于url來說,可能包含一些非法的文件命名字符,所以轉換為MD5來存儲,簡單粗暴
        md5 = hashlib.md5()
        md5.update(url)
        return md5.hexdigest()

    def is_cache(self,url):
        """判斷緩存是否存在
            返回 True 表示存在,False表示不存在"""
        cache_list = os.listdir(self.cache_name)  # 獲得緩存文件名列表
        # 獲得url的MD5碼
        md5 = self.__url_md5(url)
        if md5 in cache_list:
            print '已經緩存該 {} 網頁'.format(url)
            return True
        return False

    def save_cache(self,url):
        """保存url到緩存"""
        old_path = os.getcwd()  # 獲得當前工作目錄路徑
        os.chdir(self.cache_name)  # 改變工作目錄為緩存文件
        #print '切換后--',os.getcwd()
        with open(self.__url_md5(url), 'w'):
            pass
        print '緩存不存在,緩存 {} 網頁'.format(url)
        os.chdir(old_path)  # 切換到原始目錄

def get_html(url):
    """獲得網頁源碼"""
    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):
    """獲得當前頁所有商品的鏈接
       返回一個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 '當前頁 {} 有 {} 個商品'.format(url,len(product_list))
    return product_list

def product_detailed_info(url):
    """獲得商品的詳細信息
       商品類目,商品標題,商品價格,商品區域
       """
    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')

    #爬取一個商品詳細信息,保存一個,避免程序的以外中斷導致數據的丟失
    save_product_info(product_dict,'product.csv')
    print '保存商品信息到文件 {}'.format(url)

def save_product_info(info,file_name):
    """保存商品信息到指定文件內"""
    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()

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容