什麼?自如租房價格是圖片【一】【Python爬蟲】

前幾天有個朋友想爬一下望京附近自如租房的價格,遇到點問題,想讓我幫忙分析一下。

1 分析

我就想着,這東西我以前搞過呀,還能有啥難度不成。於是隨便打開一個租房頁面。

image.png

額(⊙o⊙)… 竟然換成了圖片。之前應該是有個單獨的Ajax請求,去獲取價格信息的。

根據頁面內容,可以看到:①雖然價格是由4個<i>標籤組成,但背景圖片卻是相同的。②有CSS可以看到,每個價格窗口的大小width: 20px; height: 30px;是固定的,僅僅設置了圖片的偏移量。

不過這也難不倒我,整理下思路:

  • 請求獲得網頁
  • 獲取圖片信息,獲取價格偏移量信息
  • 切割圖片進行識別
  • 得到價格數據

正好最近在研究CNN圖片識別相關的,這麼規整的數字,稍加訓練識別率肯定可以達到100%。

2 實戰

說幹就幹,先找個入口,然後獲取一波網頁再說。

2.1 獲取原始網頁

直接按地鐵來吧,找個15號線 望京東,然後獲取房間列表,同時再處理下分頁就好了。

image.png

示例代碼:

# -*- coding: UTF-8 -*-

import os
import time
import random

import requests
from lxml.etree import HTML

__author__ = 'lpe234'


index_url = 'https://www.ziroom.com/z/s100006-t201081/?isOpen=0'
visited_index_urls = set()


def get_pages(start_url: str):
    """
    地鐵15號線 望京東 附近房源,拿到首頁 所有詳情頁地址
    :param start_url
    :return:
    """
    # 去重
    if start_url in visited_index_urls:
        return
    visited_index_urls.add(start_url)

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
    }
    resp = requests.get(start_url, headers=headers)
    resp_content = resp.content.decode('utf-8')
    root = HTML(resp_content)
    # 解析當頁列表
    hrefs = root.xpath('//div[@class="Z_list-box"]/div/div[@class="pic-box"]/a/@href')
    for href in hrefs:
        if not href.startswith('http'):
            href = 'http:' + href.strip()
        print(href)
        parse_detail(href)
    # 解析翻頁
    pages = root.xpath('//div[@class="Z_pages"]/a/@href')
    for page in pages:
        if not page.startswith('http'):
            page = 'http:' + page
            get_pages(page)


def parse_detail(detail_url: str):
    """
    訪問詳情頁
    :param detail_url:
    :return:
    """
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
    }
    filename = 'pages/' + detail_url.split('/')[-1]
    if os.path.exists(filename):
        return
    # 隨機暫停1-5秒
    time.sleep(random.randint(1, 5))
    resp = requests.get(detail_url, headers=headers)
    resp_content = resp.content.decode('utf-8')
    with open(filename, 'wb+') as page:
        page.write(resp_content.encode())


if __name__ == '__main__':
    get_pages(start_url=index_url)

簡單獲取了一下附近的房源,共約600條。

image.png

2.2 解析網頁獲取圖片

邏輯很簡單,遍歷前面獲取的所有網頁,解析價格圖片並存儲下來。

示例代碼:

# -*- coding: UTF-8 -*-

import os
import re
from urllib.request import urlretrieve

from lxml.etree import HTML

__author__ = 'lpe234'


poss = list()


def walk_pages():
    """
    遍歷路徑所有頁面
    :return:
    """
    for dirpath, dirnames, filenames in os.walk('pages'):
        for page in filenames:
            page = os.path.join('pages', page)
            print(page)
            parse_page(page)


def parse_page(page_path: str):
    """
    解析頁面
    :param page_path:
    :return:
    """
    with open(page_path, 'rb') as page:
        page_content = ''.join([_.decode('utf-8') for _ in page.readlines()])
        root = HTML(page_content)
        styles = root.xpath('//div[@class="Z_price"]/i/@style')
        pos_re = re.compile(r'background-position:(.*?)px;')
        img_re = re.compile(r'url\((.*?)\);')
        for style in styles:
            style = style.strip()
            print(style)
            pos = pos_re.findall(style)[0]
            img = img_re.findall(style)[0]
            if img.endswith('red.png'):
                continue
            if not img.startswith('http'):
                img = 'http:' + img
            print(f'pos: {pos}, img: {img}')
            save_img(img)
            poss.append(pos)


def save_img(img_url: str):
    img_name = img_url.split('/')[-1]
    img_path = os.path.join('imgs', img_name)
    if os.path.exists(img_path):
        return
    urlretrieve(img_url, img_path)


if __name__ == '__main__':
    walk_pages()
    print(sorted([float(_) for _ in poss]))
    print(sorted(set([float(_) for _ in poss])))

最終得到價格相關圖片數據。

image.png

共21張圖片,其中橙色20張爲普通價格圖片,紅色1張爲特價房源專用。

看樣子好像用不着圖片識別了,直接按圖片名稱和偏移量進行映射就行了。

2.3 進行價格解析

本想寫識別的,感覺不咋合適,這算哪門子識別。不就是一個圖片名稱+偏移量映射麼。

示例代碼:

# -*- coding: UTF-8 -*-

import re

from lxml.etree import HTML
import requests

__author__ = 'lpe234'


PRICE_IMG = {
    '1b68fa980af5e85b0f545fccfe2f8af1.png': [8, 9, 1, 6, 7, 0, 2, 4, 5, 3],
    '4eb5ebda7cc7c3214aebde816b10d204.png': [9, 5, 7, 0, 8, 6, 3, 1, 2, 4],
    '5c6750e29a7aae17288dcadadb5e33b1.png': [4, 5, 9, 3, 1, 6, 2, 8, 7, 0],
    '6f8787069ac0a69b36c8cf13aacb016b.png': [6, 1, 9, 7, 4, 5, 0, 8, 3, 2],
    '7ce54f64c5c0a425872683e3d1df36f4.png': [5, 1, 3, 7, 6, 8, 9, 4, 0, 2],
    '8e7a6d05db4a1eb58ff3c26619f40041.png': [3, 8, 7, 1, 2, 9, 0, 6, 4, 5],
    '73ac03bb4d5857539790bde4d9301946.png': [7, 1, 9, 0, 8, 6, 4, 5, 2, 3],
    '234a22e00c646d0a2c20eccde1bbb779.png': [1, 2, 0, 5, 8, 3, 7, 6, 4, 9],
    '486ff52ed774dbecf6f24855851e3704.png': [4, 7, 8, 0, 1, 6, 9, 2, 5, 3],
    '19003aac664523e53cc502b54a50d2b6.png': [4, 9, 2, 8, 7, 3, 0, 6, 5, 1],
    '93959ce492a74b6617ba8d4e5e195a1d.png': [5, 4, 3, 0, 8, 7, 9, 6, 2, 1],
    '7995074a73302d345088229b960929e9.png': [0, 7, 4, 2, 1, 3, 8, 6, 5, 9],
    '939205287b8e01882b89273e789a77c5.png': [8, 0, 1, 5, 7, 3, 9, 6, 2, 4],
    '477571844175c1058ece4cee45f5c4b3.png': [2, 1, 5, 8, 0, 9, 7, 4, 3, 6],
    'a822d494f1e8421a2fb2ec5e6450a650.png': [3, 1, 6, 5, 8, 4, 9, 7, 2, 0],
    'a68621a4bca79938c464d8d728644642.png': [7, 0, 3, 4, 6, 1, 5, 9, 8, 2],
    'b2451cc91e265db2a572ae750e8c15bd.png': [9, 1, 6, 2, 8, 5, 3, 4, 7, 0],
    'bdf89da0338b19fbf594c599b177721c.png': [3, 1, 6, 4, 7, 9, 5, 2, 8, 0],
    'de345d4e39fa7325898a8fd858addbb8.png': [7, 2, 6, 3, 8, 4, 0, 1, 9, 5],
    'eb0d3275f3c698d1ac304af838d8bbf0.png': [3, 6, 5, 0, 4, 8, 9, 2, 1, 7],
    'img_pricenumber_detail_red.png': [6, 1, 9, 7, 4, 5, 0, 8, 3, 2]
}

POS_IDX = [-0.0, -31.24, -62.48, -93.72, -124.96, -156.2, -187.44, -218.68, -249.92, -281.16]


def parse_price(img: str, pos_list: list):
    price_list = PRICE_IMG.get(img)
    if not price_list:
        raise Exception('img not found. %s', img)
    step = 1
    price = 0
    _pos_list = reversed(pos_list)
    for pos in _pos_list:
        price += price_list[POS_IDX.index(float(pos))]*step
        step *= 10
    return price


def parse_page(content: str):
    root = HTML(content)
    styles = root.xpath('//div[@class="Z_price"]/i/@style')
    pos_re = re.compile(r'background-position:(.*?)px;')
    pos_img = re.findall('price/(.*?)\\);', styles[0])[0]
    poss = list()
    for style in styles:
        style = style.strip()
        pos = pos_re.findall(style)[0]
        poss.append(pos)
    print(pos_img)
    print(poss)
    return parse_price(pos_img, poss)


def request_page(url: str):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
    }
    resp = requests.get(url, headers=headers)
    resp_content = resp.content.decode('utf-8')
    return resp_content

爲了方便,已做成接口服務提供:測試接口==>https://lemon.lpe234.xyz/common/ziru/

3 總結

本以爲可以跟朋友展示一下新學的技能的,結果大意了。進而又在想,既然自如搞了這套東西,幹嘛不多弄點圖片素材呢,把難度提高一些。

不過爲了在朋友面前嘚瑟一下,後面咱們還是得用一下CNN,不然可就白學了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章