分散式爬取鏈家網二手房資訊
任務目標:以分散式的方式爬取鏈家網上二手房資訊,包括標題、城市、行政區、總價、戶型、面積、朝向等資訊
分散式爬蟲,即在多臺電腦上同時執行同一個爬蟲任務,在分散式爬取之前,需要先完成單機爬蟲,然後部署到多臺機器上,完成分散式。
鏈家網單機爬蟲:從城市頁面開始爬取,到每個城市的不同行政區,以及每個行政區的多個頁面,每個頁面的多個二手房資訊,到最後的二手房詳情頁面。
經過相應的網頁機構分析,得到專案(專案名lianjia、爬蟲名lj)結構及最終程式碼如下:
專案結構:
items.py
import scrapy class LianjiaItem(scrapy.Item): title = scrapy.Field() city = scrapy.Field() # 行政區 region = scrapy.Field() total_price = scrapy.Field() unit_price = scrapy.Field() # 戶型 house_type = scrapy.Field() # 朝向 orientation = scrapy.Field() full_area = scrapy.Field() inside_area = scrapy.Field() # 建築型別 building_type = scrapy.Field()
pipelines.py
import json class LianjiaPipeline: def __init__(self): self.fp = open('houses.txt', 'w', encoding='utf-8') def process_item(self, item, spider): self.fp.write(json.dumps(dict(item), ensure_ascii=False) + '\n') return item def close_spider(self, spider): self.fp.close()
由於最終要實現分散式爬取,資料是儲存在redis資料庫中,這裡先用txt檔案暫存資料,測試程式碼
settings.py
BOT_NAME = 'lianjia' SPIDER_MODULES = ['lianjia.spiders'] NEWSPIDER_MODULE = 'lianjia.spiders' ROBOTSTXT_OBEY = False CONCURRENT_REQUESTS = 5 DOWNLOAD_DELAY = 0.2 DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36' } ITEM_PIPELINES = { 'lianjia.pipelines.LianjiaPipeline': 300, }
lj.py
import scrapy
from ..items import LianjiaItem
class LjSpider(scrapy.Spider):
name = 'lj'
allowed_domains = ['lianjia.com']
start_urls = ['https://www.lianjia.com/city/']
def parse(self, response):
city_li_list = response.xpath('//div[@class="city_list_section"]//li[@class="city_list_li'
' city_list_li_selected"]//div[@class="city_list"]//ul//li')
for city_li in city_li_list:
city = city_li.xpath('.//text()').get()
city_url = city_li.xpath('.//a/@href').get()
yield scrapy.Request(url=city_url + 'ershoufang', callback=self.parse_house_region, meta={'city': city})
def parse_house_region(self, response):
position_list = response.xpath('//div[@class="position"]//div[@data-role="ershoufang"]/div//a')
for position in position_list:
position_url = response.urljoin(position.xpath('.//@href').get())
yield scrapy.Request(url=position_url, callback=self.parse_house_list, meta=response.meta)
def parse_house_list(self, response):
house_url_list = response.xpath('//ul[@class="sellListContent"]//li/a/@href').getall()
for house_url in house_url_list:
yield scrapy.Request(url=house_url, callback=self.parse_house_detail, meta=response.meta)
# 翻頁 todo
def parse_house_detail(self, response):
item = LianjiaItem()
item['title'] = response.xpath('//div[@class="sellDetailHeader"]//div[@class="title"]/h1/text()').get()
item['city'] = response.meta['city']
item['region'] = ''.join(response.xpath('//div[@class="areaName"]//span[@class="info"]//text()').getall()).strip()
item['total_price'] = response.xpath('//span[@class="total"]/text()').get() + '萬'
item['unit_price'] = response.xpath('//span[@class="unitPriceValue"]/text()').get() + '元/平米'
house_base_info = response.xpath('//div[@class="base"]//div[@class="content"]/ul//li')
item['house_type'] = house_base_info[0].xpath('./text()').get()
item['orientation'] = house_base_info[6].xpath('./text()').get()
item['full_area'] = house_base_info[2].xpath('./text()').get()
item['inside_area'] = house_base_info[4].xpath('./text()').get()
item['building_type'] = response.xpath('//div[@class="area"]//div[@class="subInfo"]/text()').get()
yield item
lianjia_main.py
from scrapy import cmdline
cmdline.execute('scrapy crawl lj'.split(' '))
執行lianjia_main.py後發現,可以正常抓取到資料儲存到houses.txt中:
接下來需實現分散式:
scrapy本身是不支援分散式的,此時需要用到scrapy_redis元件, pip install scrapy_redis 即可安裝使用
分散式爬蟲中需指定一臺機器為Redis伺服器,可以用來管理爬蟲伺服器請求的URL並去重,還可以用來儲存爬蟲伺服器爬取的資料;
需指定若干臺機器為爬蟲伺服器,用來執行爬蟲任務,從Redis伺服器獲取請求,並把爬取下來的資料傳送給Redis伺服器。
分散式爬蟲的部署準備:
-
伺服器端安裝scrapyd: pip3 install scrapyd 由於我只有一臺機器,這裡我使用的是虛擬機器下安裝的Ubuntu18.04系統
由於我已安裝過,這裡沒有再次安裝。 -
從 '/usr/local/lib/python3.6/dist-packages/scrapyd' 下拷貝出 'default_scrapyd.conf' 放到 '/etc/scrapyd/scrapyd.conf'
若提示沒有資料夾,則新建對應的路徑即可 -
修改 '/etc/scrapyd/scrapyd.conf' 中的 'bind_address' 為自己的IP(或改為 0.0.0.0 表示允許任意機器訪問)
-
在開發機(自己的windows電腦)上安裝scrapyd-client: pip install scrapyd-client
-
安裝scrapyd-client後有一個bug,無法直接使用命令scrapyd-deploy,解決辦法為:
進入安裝python的資料夾下(配置python環境變數的那個),進入Scripts資料夾下,可以看到有個scrapyd-deploy,在此資料夾下新建檔案scrapyd-deploy.bat,編輯內容為
@echo off
C:\Python37-32\python.exe C:\Python37-32\Scripts\scrapyd-deploy %*
路徑根據實際情況更改,隨後進入命令列敲入scrapyd-deploy -h,若出現相關提示資訊即表示成功
- 修改專案中的 scrapy.cfg
[settings]
default = lianjia.settings
[deploy]
url = http://192.168.1.105:6800/
project = lianjia
192.168.1.105 為我要部署該專案的爬蟲伺服器的IP
- 在專案所在路徑下執行部署命令,將專案部署到cfg檔案所配置的爬蟲伺服器: scrapyd-deploy default -p lianjia
- 通過curl命令(需下載並配置環境變數)啟動爬蟲,格式如下: curl http://localhost:6800/schedule.json -d project=myproject -d spider=somespider
分散式爬蟲的部署:
由於我只有一臺機器,系統為windows7,為了模擬分散式的部署,我使用了VMware Workstation虛擬機器,裡面有兩個Ubuntu18,一臺作為Redis伺服器,ip為192.168.1.103;一臺作為爬蟲伺服器,ip為192.168.1.105,配合本機windows7一同爬取。
在上面的部署準備完成後,就可以開始部署了:
-
首先需要待部署的伺服器開啟scrapyd服務,這裡我在 /srv 資料夾下新建資料夾lj,用來儲存部署相關資訊,在lj資料夾下輸入scrapyd,回車,即可開啟該服務
可以看到scrapyd服務的相關資訊,在瀏覽器中輸入 http://192.168.1.105:6800 出現如下頁面,表示服務開啟成功
-
在專案所在路徑下執行部署命令: scrapyd-deploy
可以看到,部署成功 -
部署成功後就可以執行爬蟲了: curl http://192.168.1.105:6800/schedule.json -d project=lianjia -d spider=lj
開啟 http://192.168.1.105:6800 ,點選jobs,可以看到爬蟲正在執行,點選log,可以檢視日誌輸出
-
此時分散式爬蟲並未部署,因為Redis伺服器還沒有配置,先關閉之前執行的爬蟲:curl http://192.168.1.105:6800/cancel.json -d project=lianjia -d job=716c852824cc11ebbc7a000c297c8a13
-
選擇ip為192.168.1.103的Ubuntu18為Redis伺服器,安裝redis:sudo apt install redis-server,然後修改配置檔案 '/etc/redis/redis.conf',取消bind註釋,修改為本機IP 102.168.1.103 或修改為 0.0.0.0,重啟服務 sudo service redis-server restart
-
修改爬蟲程式碼lj.py,使用scrapy_redis.spiders.RedisSpider替代原來的scrapy.Spider,或使用scrapy_redis.spiders.RedisCrawlSpider替代CrawlSpider,刪除start_urls並設定redis_key,這個redis_key是後面控制redis啟動爬蟲用的,爬蟲的第一個url,就是redis通過這個key傳送出去的
import scrapy
from ..items import LianjiaItem
from scrapy_redis.spiders import RedisSpider
class LjSpider(RedisSpider):
name = 'lj'
allowed_domains = ['lianjia.com']
# start_urls = ['https://www.lianjia.com/city/']
redis_key = 'lj'
def parse(self, response):
......
- 在settings.py中對scrapy_redis進行相關配置
刪除原來的pipeline配置並進行如下配置
# 確保request儲存到redis中
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 確保所有爬蟲共享相同的去重指紋
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 設定redis為item pipeline
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300,
}
# 是否在關閉時候保留原來的排程器和去重記錄,True=保留,False=清空
SCHEDULER_PERSIST = True
EDIS_HOST = '192.168.1.103'
REDIS_PORT = 6379
- 重新部署專案並執行: 首先將專案重新部署到192.168.1.105的爬蟲伺服器上並執行,然後在本地windows上也執行該專案,檢視日誌,可以看到,兩個爬蟲專案都沒有開始爬取資料,而是等待Redis伺服器的排程開始
- 進入Redis伺服器,推入一個開始的url連結: redis-cli>lpush [redis_key] start_url 開始爬取
可以看到,兩個爬蟲伺服器同時開始爬取資料了,實現了分散式的爬取
- 檢視redis伺服器內的資料:
可以看到,有三種key:lj:requests、lj:dupefilter、lj:items,分別表示請求資訊、去重過濾資訊、爬取的資料資訊
檢視 lj:items 可以看到,裡面已經儲存了爬取到的資料,至此,分散式爬取鏈家網二手房資料的專案完成。