目标
此次爬取的网站是楼盘网,因为只爬取小区信息,所以先从深圳小区(http://sz.loupan.com/community/)网页入手分析,然后发散爬取至全国。
爬取的信息包括 省,所属市,所属区,小区名,小区链接,详细地址,经纬度,交通,价格,物业类型,物业价格,面积,户数,竣工时间,车位数,容积率,绿化率,物业公司,开发商。
保存至Excel和mysql,也可以保存至MongoDB,看个人需求。
字段目录
效果
mysql表:
excel表:
配置环境
电脑:win10 64x
python版本:Python 3.7.5 (tags/v3.7.5:5c02a39a0b, Oct 15 2019, 00:11:34) [MSC v.1916 64 bit (AMD64)] on win32
工具:JetBrains PyCharm Community Edition 2019.2.5 x64
scrapy框架:
分析
为了保证分工明确,提高爬取效率,此次爬取采用python的scrapy框架,因为scrapy自带dupefilters.py去重器,可以不用担心重复爬取。scrapy运行机制如下:
Scrapy运行时,请求发出去的整个流程大概如下:
1.首先爬虫将需要发送请求的url(requests)经引擎交给调度器;
2.排序处理后,经ScrapyEngine,DownloaderMiddlewares(有User_Agent, Proxy代理)交给Downloader;
3.Downloader向互联网发送请求,并接收下载响应.将响应经ScrapyEngine,可选交给Spiders;
4.Spiders处理response,提取数据并将数据经ScrapyEngine交给ItemPipeline保存;
5.提取url重新经ScrapyEngine交给Scheduler进行下一个循环。直到无Url请求程序停止结束。
首先创建scrapy工程
爬城市
首先根据网页,获取所有城市的链接
然后就可以多线程的同时爬取所有城市小区,我分析了网页结构,单纯的爬取本页是获取不了城市链接的,城市链接使用的是JavaScript加载的动态数据,既然如此那就获取他的json数据,如下:
右键获取网址:http://sz.loupan.com/index.php/jsdata/common?_=1579245949843,这是一个json格式列表,我们已经获取所有城市的链接目录,但是这个目录有很多无用网址,所以要进行数据清洗,把无用数据清洗掉。
这里使用requests方法获取网页信息,然后使用正则获取链接,再使用for循环清洗数据。
比如深圳市:http://sz.loupan.com/
既然已经获取了所有城市链接,那就开始正式爬取网页上的小区。
爬小区
首先在http://sz.loupan.com/community/这一页,我看到,一页有25个小区,然后总共只有100页,
也就是说,如果我按照传统的遍历每一页去爬的话,最多只能爬取2500条数据。
但是,实际上可以看到深圳小区数量是10243
所以,这个时候我转换思路,我随机点开一个小区链接,拉下来可以看到,有一个周边小区推荐
我可以依靠这个,然后不断循环遍历,直至最终爬完所有小区,这也是很多爬虫所运用的方法,包括像爬新浪微博所有用户信息,爬取知乎所有用户信息,都可以从某一个大v出发,然后爬取其关注列表和被关注列表,然后不断循环发散,直至爬完整个新浪用户。
在http://sz.loupan.com/community页面按F12查看深圳小区第一页网页源码,很容易得到网页所有小区链接,25条,然后遍历这25各小区,在每个小区里再遍历周边小区。
好,现在可以爬完所有小区信息了,那问题来了,重复的怎么办,scrapy自带dupefilters.py去重器,可以不用担心重复爬取。
scrapy去重原理:
1.Scrapy本身自带有一个中间件;
2.scrapy源码中可以找到一个dupefilters.py去重器;
3.需要将dont_filter设置为False开启去重,默认是True,没有开启去重;
4 .对于每一个url的请求,调度器都会根据请求得相关信息加密得到一个指纹信息,并且将指纹信息和set()集合中的指纹信息进 行 比对,如果set()集合中已经存在这个数据,就不在将这个Request放入队列中;
5.如果set()集合中没有存在这个加密后的数据,就将这个Request对象放入队列中,等待被调度。
很明显,小区链接就是城市链接+community+小区编号,community是小区的意思。
class XiaoquSpiderSpider(scrapy.Spider):
name = 'xiaoqu_spider'
# 获取所有城市链接
url = 'http://sz.loupan.com/index.php/jsdata/common?_=1579245949843'
response = requests.get(url).text
urls = list(set(re.findall('http://\w+?.loupan.com', response)))
url_delete = (
'http://app.loupan.com', 'http://www.loupan.com', 'http://public.loupan.com', 'http://user.loupan.com')
for url in urls:
if url in url_delete:
urls.remove(url)
我这里使用pyquery库的PyQuery方法获取小区链接。
获取链接后,要开始分析小区详情的网页。
同样方法,F12查看网页源码,然后获取小区的详细信息
def parse(self, response):
doc = pq(response.text)
item = XiaoquItem()
url = doc('.pos > a:nth-child(4)').attr('href') # 小区链接
item['url'] = url # 小区链接
name = doc('.t p').text() # 小区名
item['name'] = name # 小区名
# 根据网页获得小区模糊地址,再通过百度地图API获取经纬度
addres = doc('.text_nr.bug2').text() # 小区地址
citys = doc('.pos > a:nth-child(2)').text()
city = ''.join(re.findall('(\w+)小区', citys)) + '市'
districts = doc('span.font_col_o > a').text() # 所属区
address = city + districts + addres + name # 所属详细地址
# 将地址传入api获取省市区
location = self.location(address)
coord = location['coord'] # 经纬度
item['coord'] = coord
province = location['province'] # 省
item['province'] = province
city = location['city'] # 市
item['city'] = city
district = location['district'] # 区
item['district'] = district
item['detail_address'] = province + city + district + addres + name # 详细地址
id = ''.join(re.findall('\d+', url))
around_url = 'http://sz.loupan.com/community/around/' + id + '.html' # 周边信息网址
response = requests.get(around_url)
around_doc = pq(response.text)
traffic = around_doc('.trend > p:nth-child(7)').text() # 交通
item['traffic'] = traffic.replace('m', 'm,') # 交通
prices = doc('div.price > span.dj').text() # 参考价格
if prices == '暂无数据':
price = None
item['price'] = price
else:
price = int(prices)
item['price'] = price
item['property_type'] = doc('ul > li:nth-child(1) > span.text_nr').text