1. 引言

Paste_Image.png
| 標題 | 說明 |
|---|---|
| 網(wǎng)址 | http://sh.ganji.com/wu/ |
| 要求1 | 點進來, 拉到頁面底部 |
| 要求2 | 需要爬取趕集網(wǎng)-上海-二手市場的所有類目的商品信息 |
| 要求3 | 點進來的列表頁, 抓取個人類目下的全部帖子 |

Paste_Image.png

Paste_Image.png
2. 分析
- 分類查找:
檢查元素, 輸入
.fenlei > dt > a可以等到全部分類鏈接
Paste_Image.png
- 分類列表:
- 類目中的個人列表頁: http://sh.ganji.com/ershoubijibendiannao/o2/
頁數(shù)隨最后一個數(shù)字變化
多了
a2字符, 頁數(shù)隨最后一個數(shù)字變化
- 列表頁中鏈接查找:
.ft-tit找到全部鏈接, 接著過慮掉包含click關鍵字的推廣商品和zhuanzhuan商品
Paste_Image.png
- 列表尾部:
只有5個條目:
Paste_Image.png
- 已賣完商品返回的頁面狀態(tài)碼為
404 - 詳情信息抓取采用斷點續(xù)傳和多進程
3. 實現(xiàn)部分
3.1 基礎模塊
# vim spider_ganji.py // 基礎模塊
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'jhw'
from bs4 import BeautifulSoup
from pymongo import MongoClient
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36'
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh',
'Connection': 'keep-alive',
}
# 自定義要抓取區(qū)域, 拼音簡寫
locale = 'sh'
client = MongoClient('10.66.17.17', 27017)
database = client['ganji']
# 區(qū)域分表存入mongodb
url_info = database['{}_ershou_url'.format(locale)]
# 已抓取的url存入此表
url_info_exists = database['{}_ershou_url_exists'.format(locale)]
# 商品信息存儲表
item_info = database['{}_ershou_item'.format(locale)]
# 將要抓取的全部url放入此列表中
url_info_list = [item['url'] for item in url_info.find()]
# 抓取完畢的url放入此列表中
url_info_exists_list = [item['url'] for item in url_info_exists.find()]
# 定義獲取主分類鏈接的函數(shù)
def get_url_index(locale):
# 鏈接根據(jù)上面給定的區(qū)域決定
url = 'http://{}.ganji.com/wu/'.format(locale)
data = requests.get(url, headers=headers)
soup = BeautifulSoup(data.text, 'lxml')
links = soup.select('.fenlei > dt > a')
# 定義存儲分類鏈接的列表
L = []
for link in links:
# 組合一條完整的分類鏈接
link_pre = url.split('wu/')[0]+'{}/'.format(link.get('href').strip('/'))
# 將分類鏈接存入分類列表
L.append(link_pre)
# 返回分類鏈接列表, 用作多進程
return L
# 定義循環(huán)獲取全部url的商品信息函數(shù), 主分類鏈接傳入
def get_url_from_list(url, who_sells=''):
# 主分類鏈接循環(huán)加入頁數(shù)
for page in range(1, 200):
# 一條完整的帶有頁數(shù)的商品鏈接
url_full = url + '{}o{}'.format(who_sells, page)
# 執(zhí)行獲取商品信息函數(shù), 同時返回執(zhí)行狀態(tài)碼
code = get_url_info(url_full, page)
# 返回的條目少于10則判斷到了頁面尾部, 退出循環(huán)
if code < 10:
print('$'*20, '%s End of the pages!!!' % url_full, '$'*20, '\n\n')
break
print('\n')
# 定義獲取各分類頁中商品鏈接的函數(shù)
def get_url_info(url, page):
# 截取出鏈接屬于哪個分類
cate = url.split('/')[-2]
data = requests.get(url, headers=headers)
# 講求失敗則退出
if data.status_code != 200:
print('%s Request Error!!!' % data.status_code)
else:
soup = BeautifulSoup(data.text, 'lxml')
# links = soup.select('.ft-db ul li > a')
links = soup.select('.ft-tit')
# judge = len(soup.select('dl.list-bigpic'))
# 獲取頁面中商品的總條目
judge = len(links)
# 如果商品總條目為5則判斷此頁面是列表頁的最后一頁, 不再抓取
if judge == 5:
print(cate, '-', page, "Error, We can't find anything because there is nothing to be found.")
else:
for i in links:
link = i.get('href')
# 推廣商品和轉轉商品過慮掉
if 'click' not in link and 'zhuanzhuan' not in link:
# 商品鏈接存入mongodb中負責存儲商品鏈接的表中
url_info.insert_one({'url': link})
# 打印目前抓取的是哪個分類中的哪一頁
print(cate, '-', page, '=>', link)
# 返回此頁商品的總條目, get_url_from_list會用到
return judge
# 定義獲取商品詳情頁信息的函數(shù)
def get_item_from(url):
# 如果商品之前已經(jīng)抓取過則退出
if url in url_info_exists_list:
print('#'*20, '"%s" has been opened before...' % url, '#'*20)
else:
print(url)
data = requests.get(url, headers=headers)
# 頁面請求錯誤則退出
if data.status_code != 200:
print('E'*20, '%s request error...', 'E'*20)
# 頁面返回404表示商品已賣完, 退出
elif data.status_code == 404:
print('W'*20, 'This article has been to Mars...', 'W'*20)
else:
soup = BeautifulSoup(data.text, 'lxml')
# 商品標題
titles = soup.select('.title-name')
# 商品發(fā)布時間
updates = soup.select('.pr-5')
# views = soup.select('#pageviews')
# 商品類型
types = soup.select('.det-infor > li:nth-of-type(1) > span')
# 商品價格
prices = soup.select('.f22')
# 商品區(qū)域
areas = soup.select('.det-infor > li:nth-of-type(3) > a')
# 商品成色
degrees = soup.select('.second-det-infor > li')
# 有的商品沒有發(fā)布時間, 還有時請求網(wǎng)頁獲取的時間有錯, 暫時先這么判斷
if updates:
if len(updates[0].get_text()) <= 3:
update = None
else:
update = updates[0].get_text().strip().split()[0]
else:
update = None
data = {
'title': titles[0].get_text() if titles else None,
'update': update,
'type': types[0].get_text().replace('\n', '').replace(' ', '') if types else None,
'price': int(prices[0].get_text()) if prices else 0,
'area': [i.get_text().strip() for i in areas] if areas else None,
'degree': degrees[0].get_text().split('\n')[-1].replace(' ', '') if degrees else None,
'cate': url.split('/')[-2],
}
print(data)
# 商品信息存入mongodb
item_info.insert_one(data)
# 商品信息抓取完后, 將此商品的鏈接存入mongodb中存放已經(jīng)抓取完畢的url表中
url_info_exists.insert_one({'url': url})
# 獲取主分類鏈接列表
url_index = get_url_index(locale)
3.2 抓取全部商品鏈接
# vim main.py //程序入口
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = 'jhw'
# 從自定義模塊中導入獲取商品鏈接的函數(shù)和主分類列表
from spider_ganji import get_url_from_list, url_index
# 從自定義模塊中導入獲取商品詳情的函數(shù)和商品鏈接列表
# from spider_ganji import get_item_from, url_info_list
# 導入多進程模塊
from multiprocessing import Pool
if __name__ == '__main__':
pool = Pool()
# 多進程獲取全部商品鏈接
pool.map(get_url_from_list, url_index)
# 多進程獲取全部商品詳情信息
# pool.map(get_item_from, url_info_list)
# 調(diào)用join()之前必須先調(diào)用close(), 調(diào)用close()之后就不能繼續(xù)添加新的Process了
pool.close()
# 對Pool對象調(diào)用join()方法會等待所有子進程執(zhí)行完畢
pool.join()
# python3 main.py // 開啟多進程抓取商品鏈接, 4核CPU開啟了4個進程
shouji - 2 => http://sh.ganji.com/shouji/1525463821x.htm
shouji - 2 => http://sh.ganji.com/shouji/1637265573x.htm
shouji - 2 => http://sh.ganji.com/shouji/1469334107x.htm
.
jiadian - 3 => http://sh.ganji.com/jiadian/2181455484x.htm
jiadian - 3 => http://sh.ganji.com/jiadian/2181302594x.htm
jiadian - 3 => http://sh.ganji.com/jiadian/1899011509x.htm
.
jiaju - 3 => http://sh.ganji.com/jiaju/2182971064x.htm
jiaju - 3 => http://sh.ganji.com/jiaju/2170617991x.htm
jiaju - 3 => http://sh.ganji.com/jiaju/2109235459x.htm
.
bangong - 3 => http://sh.ganji.com/bangong/2050123549x.htm
bangong - 3 => http://sh.ganji.com/bangong/2207236120x.htm
bangong - 3 => http://sh.ganji.com/bangong/1880908704x.htm
3.3 抓取全部商品詳情信息
# vim main.py // 程序入口
# -*- coding: utf-8 -*-
__author__ = 'jhw'
# 從自定義模塊中導入獲取商品鏈接的函數(shù)和主分類列表
# from spider_ganji import get_url_from_list, url_index
# 從自定義模塊中導入獲取商品詳情的函數(shù)和商品鏈接列表
from spider_ganji import get_item_from, url_info_list
# 導入多進程模塊
from multiprocessing import Pool
if __name__ == '__main__':
pool = Pool()
# 多進程獲取全部商品鏈接
# pool.map(get_url_from_list, url_index)
# 多進程獲取全部商品詳情信息
pool.map(get_item_from, url_info_list)
# 調(diào)用join()之前必須先調(diào)用close(), 調(diào)用close()之后就不能繼續(xù)添加新的Process了
pool.close()
# 對Pool對象調(diào)用join()方法會等待所有子進程執(zhí)行完畢
pool.join()
# python3 main.py //開啟多進程抓取商品詳情信息, 4核CPU開啟了4個進程
#################### "http://sh.ganji.com/meironghuazhuang/2197468267x.htm" has been opened before... ####################
#################### "http://sh.ganji.com/meironghuazhuang/2119557406x.htm" has been opened before... ####################
#################### "http://sh.ganji.com/meironghuazhuang/2118152395x.htm" has been opened before... ####################
#################### "http://sh.ganji.com/xianzhilipin/2022660697x.htm" has been opened before... ####################
.
http://sh.ganji.com/jiadian/2196681538x.htm
{'title': '出售5.2KG海爾滾筒洗衣機 - 500元', 'type': '大家電', 'degree': None, 'price': 500, 'cate': 'jiadian', 'update': '07-06', 'area': ['上海', '長寧', '中山公園']}
http://sh.ganji.com/jiadian/2205076770x.htm
{'title': '品牌辦公家具大量低價拋售!震旦、美時、歐美、勵致、天壇等! - 88元', 'type': '其他辦公家具', 'degree': '95成新,可送貨', 'price': 88, 'cate': 'bangong', 'update': None, 'area': ['上海', '浦東', '八佰伴']}
.
http://sh.ganji.com/bangong/1660785908x.htm
{'title': '進口音箱功放機投影機空調(diào)3P紅木辦公桌書柜茶桌房子賣了搬家。 - 2500元', 'type': '書柜', 'degree': '95成新,可送貨', 'price': 2500, 'cate': 'jiadian', 'update': '07-06', 'area': ['上海', '浦東']}
http://sh.ganji.com/jiadian/2205106758x.htm
{'title': '前臺,移動柜子,辦公椅子 - 300元', 'type': '前臺桌', 'degree': '8成新,不包送貨', 'price': 300, 'cate': 'jiaju', 'update': '07-05', 'area': ['上海', '普陀', '曹楊新村']}
4. 總結
- 多進程利用
map由主函數(shù)作用于列表 - 循環(huán)執(zhí)行的程序可由返回的狀態(tài)來決定退出與否
-
Pool的默認大小是CPU的核心數(shù),因此,最多同時執(zhí)行4個進程。這是Pool有意設計的限制,并不是操作系統(tǒng)的限制。如果改成:
p = Pool(5)
就可以同時跑5個進程。


