爬取链家所有房源信息(在售、成交、租房)

环境:Windows10+Anaconda python3.6.5+Spyder
目标:抓取链家北京地区所有房源信息。

打开链家官网 https://bj.lianjia.com/ 。粗略的浏览了一下,整个网站使用的都是静态网页,通过改变url来实现网页变动。不过网站默认只显示100页的内容,每页30条,不管那里写着多少多少条数据。

如果需要抓取所有数据,必须通过某些条件筛选。有地区、价格、朝向、楼层等,这里面我选择了地区,因为这是区分度比较明确的。地区就那么多,一次获取,可以同时用在在售、成交和租房。

先获取首页的地区对应的url(每一个地区都是超链接),如下图:
在这里插入图片描述
通过url再获取进一步区域的url,如下图:
在这里插入图片描述
虽然这里显示到香河,但是点击香河发现还有区域在其他的网页,这里只能将url加入一个列表,然后判断新的url是否在列表里,有就舍弃,没有则添加并获取进一步区域的信息。(而且这里还有一个很奇怪,我爬取的是北京的,所有url的host应该是bj.lianjia.com但香河的host确是lf.lianjia.com。后面很多url都是相对路径需要手动添加host,于是又多了一步host的判断)

第一步抓取的信息如下图(这一次抓取并没有什么阻碍,全部获取到):
在这里插入图片描述
这里插一个经验:我看到很多人在这里直接将url设为_id ,这样可以直接达到去重的效果,但需要捕获异常,还有一种是update,效率是真的低的可怕(我更新的是几百万条的数据)。所以我觉得还是直接将url设为_id 去重好一点。(如果有更好的方法,还请留言指教)

第二次抓取的信息(小区),这一次抓取,sleep时间无论是随机2-5秒还是5秒都会在抓取几条之后返回404,这样只能使用代理和随机头信息。不过我没有代理,以前写的aiohttp测试的代理,居然不支持https的网站,http的网站一切正常(还是我大requests强大,就是有点慢),而且居然还有地区的小区数量大于3000,算了就这样吧:
在这里插入图片描述
第三步则抓取小区下的房源信息:
在这里房源url是有规律的,所有没有必要访问小区的url。
小区的url格式:https://bj.lianjia.com/xiaoqu/1111027378891/
二手房成交url:https://bj.lianjia.com/chengjiao/c1111027378891/
在售url:https://bj.lianjia.com/ershoufang/c1111027378891/
租房url:https://bj.lianjia.com/zufang/c1111027380887/
所以只需要正则获取后面的一串数字,然后构造就行。这样还能将chengjiao的那部分设为变量,然后三个网站只需要一个程序就行。(而且在多次尝试发现,小区的url里面可能没有成交房源的信息,但手工构造的url却能访问,也包含正常信息,这就有点坑了)

第一步代码:

date: Fri Nov 23 10:41:25 2018
python: Anaconda 3.6.5
author: kanade
email: [email protected]
"""
import re
import requests
import pyquery
import pymongo

class Area(object):
    '''
    获取北京链家的所有地区的url
    '''
    def __init__(self):
        self.start_url = 'https://bj.lianjia.com/xiaoqu/'
        self.client = pymongo.MongoClient()
        self.db = self.client['lianjia']['area']
        self.regex = re.compile(r'\w\w\.lianjia\.com') # 提取host的正则
    
    def get_html(self, url, session):
        '''
        请求url,返回host和响应内容
        '''
        # 提取url中的host作为请求头参数
        host = re.search(self.regex, url).group()
        headers = {
            'Host':host,
            'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.61 Safari/537.36'
         }
        resp = session.get(url, headers=headers)
        if resp.status_code == 200:
            return host, resp.text
    
    def extract_html(self, html, query_rule):
        '''
        使用pyquery提取网页中的区域和它相对应的url
        '''
        host, html = html
        doc = pyquery.PyQuery(html)
        items = doc(query_rule).items()
        for item in items:
            area = item.text()
            url = item.attr.href
            if url.startswith('/'):
                url = 'https://' + host + url
            yield area, url
            
    def run(self):
        '''
        运行函数,因为最多只有两页地区信息,所以只有两个循环
        '''
        session = requests.Session()
        html = self.get_html(self.start_url, session)
        self.area_url = list(self.extract_html(html, 'div[data-role="ershoufang"] a'))
        # 获取详细的小区信息,并存储到MongoDB
        for area, url in self.area_url:
            html = self.get_html(url, session)
            xiaoqu = self.extract_html(html, 'div[data-role="ershoufang"] div:last-child a')
            for sub_area, sub_url in xiaoqu:
                d = {'area':area,
                      'xiaoqu':sub_area,
                      'url':sub_url
                    }
                self.db.insert_one(d)
            #time.sleep(0.5)
            
        # html为上一次循环最后一个网页,再提取一遍地区,如果和上次相同则舍弃,否则获取小区
        for area_url in self.extract_html(html, 'div[data-role="ershoufang"] div:first-child a'):
            if area_url not in self.area_url:
                area, url = area_url
                html = self.get_html(url, session)
                xiaoqu = self.extract_html(html, 'div[data-role="ershoufang"] div:last-child a')
                for sub_area, sub_url in xiaoqu:
                    d = {'area':area,
                          'xiaoqu':sub_area,
                          'url':sub_url
                        }
                    self.db.insert_one(d)
            
if __name__ == '__main__':
    a = Area()
    a.run()

第二步代码:

# -*- coding: utf-8 -*-
"""
date: Fri Nov 23 15:07:38 2018
python: Anaconda 3.6.5
author: kanade
email: [email protected]
"""
import re
import time
import random
import requests
import pyquery
import pymongo


class XiaoQu(object):
    '''
    获取地区下的所有小区的url
    '''
    def __init__(self):
        self.client = pymongo.MongoClient()
        self.db_xiaoqu = self.client['lianjia']['xiaoqu']
        self.regex = re.compile(r'\w\w\.lianjia\.com')
        
    def random_time(self):
        i = random.randint(2, 4)
        j = random.random()
        return i + j
        
    def get_html(self, url, session):
        host = re.search(self.regex, url).group()
        headers = {
            'Host':host,
            'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.61 Safari/537.36'
         }
        resp = session.get(url, headers=headers)
        print(resp.status_code)
        if resp.status_code == 200:
            return host, resp.text
        
    def run_one(self, url):
        session = requests.Session()
        html = self.get_html(url, session)
        doc = pyquery.PyQuery(html[1])
        count = int(doc('.resultDes.clear h2.total span').text().strip())
        if count == 0:
            return
        self.extract(html)
        if count % 30 == 0:
            count = count // 30
        else:
            count = count //30 + 1
        time.sleep(2)
        for page in range(2, count+1):
            if url.endswith('/'):
                url += 'pg' + str(page) + '/'
            else:
                url += '/pg' + str(page) + '/'
            html = self.get_html(url, session)
            self.extract(html)
            t = self.random_time()
            time.sleep(t) 
            
    def run(self):    
        db_area = self.client['lianjia']['area']
        for data in db_area.find().skip(222):
            print(data['area'],data['xiaoqu'])
            self.run_one(data['url'])
        
        
    def extract(self, html):
        host, html = html
        doc = pyquery.PyQuery(html)
        data = []
        
        items = doc('li.xiaoquListItem').items()
        for item in items:
            name = item.find('.title a').text()
            url = item.find('.title a').attr.href
            if url.startswith('/'):
                url = 'https://' + host + url
            d = {'name':name,
                 'url':url
                  }
            data.append(d)
        self.db_xiaoqu.insert_many(data)
        


if __name__ == '__main__':
    xq = XiaoQu()
    xq.run()

第三步代码:

# -*- coding: utf-8 -*-
"""
date: Sat Nov 24 11:10:05 2018
python: Anaconda 3.6.5
author: kanade
email: [email protected]
"""
import time
import re
import requests
import pyquery
import pymongo


class FangYuan(object):
    '''
    从数据库拿到小区的链接,然后抓取小区的房源信息
    '''
    def __init__(self):
        client = pymongo.MongoClient()
        self.db = client['lianjia']
        self.regex = re.compile(r'\w\w\.lianjia\.com')
    
    def get_html(self, url, session):
        host = re.search(self.regex, url).group() 
        headers = {
            'Host':host,
            'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.61 Safari/537.36'
         }
        resp = session.get(url, headers=headers)
        print(resp.status_code)
        if resp.status_code == 200:
            return resp.text
        
    def run_one(self, url):
        host = re.search(self.regex, url).group() 
        url_id = re.search(r'/(\d+)', url).group(1)
        url = 'https://' + host + '/chengjiao' + '/c' + url_id + '/'
        
        session = requests.Session()
        html = self.get_html(url, session)
        doc = pyquery.PyQuery(html)
        count = int(doc('.resultDes.clear .total span').text().strip())
        if not html or count == 0:
            return
        self.extract(html, host)
        if count % 30 == 0:
            count = count // 30
        else:
            count = count // 30 + 1
        time.sleep(2)
        for page in range(2, count+1):
            url = 'https://' + host + '/chengjiao'+\
                        '/pg'+str(page)+'c'+url_id +'/'
            html = self.get_html(url, session)
            self.extract(html, host)
            time.sleep(3)
    
    def run(self):
        for i in self.db['xiaoqu'].find():
            self.run_one(i['url'])
        
    def extract(self, html, host):
        doc = pyquery.PyQuery(html)
        items = doc('ul.listContent li').items()
        for item in items:
            img = item.find('.img').attr.href
            title = item.find('.title a').text()
            url = item.find('.title a').attr.href
            time_ = item.find('.address .dealDate').text()
            price = item.find('.address .totalPrice').text()
            every_price = item.find('.flood unitPrice').text()
            
            describe_1 = item.find('.address .houseInfo').text()
            describe_2 = item.find('.flood .positionInfo').text()
            describe_3 = item.find('.dealHouseInfo').text()
            describe_4 = item.find('.dealCycleeInfo').text()
            
            describe = describe_1.strip() + '\n' +  describe_2.strip() +\
                            '\n' + describe_3.strip() +'\n'+ describe_4.strip()
            if url.startswith('/'):
                url = 'https://' + host + url
            d = { 'img':img,
                  'title':title,
                  'url':url,
                  'time':time_,
                  'price':price,
                  'every_price':every_price,
                  'describe':describe
                  }
            self.db['fangyuan'].insert_one(d)
        
        
        
if __name__ == '__main__':
    fy = FangYuan()
    fy.run_one('https://bj.lianjia.com/xiaoqu/1111027378889/')  
    #fy.run()           
          

如果想要全部房源信息,需要加入代理。我就不加了,没加代理很快就会返回404。因为只是学习思路,这个爬虫并不成熟,没有异常处理,也没有特殊情况处理,而且爬虫还可以在宏观一点,获取每个城市的url。有能力或者需求的可以自行添加。

猜你喜欢

转载自blog.csdn.net/Qwertyuiop2016/article/details/84445010