要求:
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文件内。如下:
具体代码如下:
#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()
网友评论