前几天有个敌人想爬一下望京左近自若租房的价格,遇到点问题,想让我帮忙剖析一下。
1 剖析
我就想着,这货色我以前搞过呀,还能有啥难度不成。于是轻易关上一个租房页面。
额(⊙o⊙)… 居然换成了图片。之前应该是有个独自的 Ajax 申请,去获取价格信息的。
依据页面内容,能够看到:①尽管价格是由 4 个 <i>
标签组成,但背景图片却是雷同的。②有 CSS 能够看到,每个价格窗口的大小 width: 20px; height: 30px;
是固定的,仅仅设置了图片的偏移量。
不过这也难不倒我,整顿下思路:
- 申请取得网页
- 获取图片信息,获取价格偏移量信息
- 切割图片进行辨认
- 失去价格数据
正好最近在钻研 CNN 图片辨认相干的,这么规整的数字,稍加训练识别率必定能够达到 100%。
2 实战
说干就干,先找个入口,而后获取一波网页再说。
2.1 获取原始网页
间接按地铁来吧,找个15 号线 望京东
,而后获取房间列表,同时再解决下分页就好了。
示例代码:
# -*- 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 条。
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])))
最终失去价格相干图片数据。
共 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,不然可就白学了。