五八同城租房爬虫 项目总结

项目概览

  • 目标:抓取 58 同城租房详情页信息,并入库 Oracle。
  • 语言/版本:Python 2.7
  • 模块分层:
    • 入口:run.py 启动任务
    • 抓取调度:reptile_58.py(分页、链接提取、重试与限速)
    • 网络请求:python_web.py(urllib2 + UA 伪装)
    • 解析入库:regexp_house.py(正则解析)→ insert_oracle.pypy_oracle.py

运行入口:

1
2
3
4
5
6
7
8
9
10
11
import reptile_58, time

class start_crawl(object):
def __init__(self):
print "开始爬取58同城租房信息,请稍后····"
time.sleep(2)
reptile_58.pretend_web().inital_58_url()
print "58同城租房信息爬取已完成!"

if __name__ == "__main__":
start_crawl()

网络请求与 UA 伪装

1
2
3
4
5
6
7
8
9
10
11
12
import urllib2

class reptile_web(object):
def getpage(self, url):
try:
headers = {"user-Agent": "Mozilla/4.0 (compatible;MSIE 8.0 ; Windows /VT 6.1)"}
req = urllib2.Request(url, headers=headers)
response = urllib2.urlopen(req)
return response.read()
except Exception, e:
e.__str__()
# 返回 None 触发上层重试

要点:统一 UA;请求失败返回 None,由上层控制重试与退避。

分页与链接提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import re, time, random, python_web

class pretend_web(object):
def __init__(self):
self.rp = python_web.reptile_web()

def inital_58_url(self):
page = 1
while True:
if page < 71:
html = self.rp.getpage("http://bj.58.com/chuzu/pn%s/" % page)
self.start_rp(html)
page += 1
elif page == 71:
print "已经到了最后一页!"; break

def start_rp(self, html):
pat = re.compile('<li\s*logr.*?<div\s*class="des".*?<h2>.*?'
'<a\s*href="(.*?)"\s*class=.*?tongji.*?'</n+ '</h2>.*?</li>', re.S)
url_list = re.findall(pat, html)
print "当前页面有{}条租房信息!".format(len(url_list))
if url_list:
self.run_page(url_list)
else:
# 多次重试 + 间隔
time.sleep(30)
# ... 重试三次,仍失败则放弃

def run_page(self, url_list):
for u in url_list:
url = "http:" + u
sub = self.rp.getpage(url)
if not sub:
# 最多重试 3 次
for _ in range(3):
time.sleep(20)
sub = self.rp.getpage(url)
if sub: break
else:
continue
# 解析 + 入库
regexp_house.regexp_list().matching_class(sub)
time.sleep(random.randint(5, 8)) # 访问节流

要点:

  • 列表页用正则提取详情链接;
  • 失败重试(20/30 秒退避)+ 详情访问 5–8 秒随机等待;
  • 页码上限到 70 页(page < 71)。

详情解析与入库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import re, insert_oracle

class regexp_list(object):
def matching_class(self, html):
# 租金与支付方式
rent_info = re.search(r'<div\s*class="main-wrap">.*?'
r'<div\s*class="house-pay-way\s*.*?'
r'<b\s*class=.*?>([0-9]*)</b>.*?'
r'<span\s*class=.*?>(.*?)</span>.*?', html, re.S)
# 租赁方式
lease_way = re.search(r'租赁方式.*?<span>(.*?)</span>', html, re.S)
# 房屋类型
house_type = re.search(r'房屋类型.*?class="strongbox">(.*?)\s*&', html, re.S)
# 小区名
houses_name = re.search(r'所在小区.*?onclick.*?">(.*?)</a>', html, re.S)
# 详细地址
houses_address = re.search(r'详细地址.*?class="dz"\s*>\s*(.*?)\s*</span>', html, re.S)
# 联系人
linkman = re.search(r'class="agent-name.*?onclick.*?">(.*?)</a>', html, re.S)
# 电话
phone = re.search(r'class="house-chat-phone.*?strongbox">([0-9]*|.*?)</span>', html, re.S)

if all([rent_info, lease_way, house_type, houses_name, houses_address, linkman, phone]):
t = (rent_info.group(1).strip(), rent_info.group(2).strip(),
lease_way.group(1).strip(), house_type.group(1).strip(),
houses_name.group(1).strip(), houses_address.group(1).strip(),
linkman.group(1).strip(), phone.group(1).strip())
insert_oracle.insert_into_oracle().into_code(*t)

入库实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os, py_oracle
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8' # 解决中文乱码

class insert_into_oracle(object):
def __init__(self):
self.orcl = py_oracle.p_oracle()
self.orcl.create_table() # 如存在会回滚

def into_code(self, t1,t2,t3,t4,t5,t6,t7,t8):
sql = (
"insert into reptile_project_58(house_rent,pay_way,lease_way, house_type," \
"houses_name, houses_address,linkman,phone) "
"VALUES('%s','%s','%s','%s','%s','%s','%s','%s')" % (t1,t2,t3,t4,t5,t6,t7,t8)
)
try:
self.orcl.orcl_commit(sql)
self.orcl.orcl_close()
print "插入数据库完成!"
except:
print "插入数据库出现错误,跳过本次插入!"; self.orcl.orcl_roll()

数据库连接与建表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cx_Oracle

class p_oracle(object):
def __init__(self):
self.db = cx_Oracle.connect("t1", "t1", "127.0.0.1:1521/orcl")
self.cursor = self.db.cursor()

def create_table(self):
sql_cols = (
"house_rent number(35), pay_way varchar2(50), lease_way varchar2(50), "
"house_type varchar(50), houses_name varchar2(50), houses_address varchar2(50), "
"linkman varchar2(50), phone number(11)"
)
sql = "create table reptile_project_58 (%s)" % sql_cols
try:
self.cursor.execute(sql); self.db.commit()
except Exception, e:
self.db.rollback()

总结

  • 请求层
    • 失败重试与退避已实现;可加入超时、代理池与 UA 轮换,提高稳定性。
    • 建议切换到 requests,更易设置超时/代理/重试。
  • 解析层
    • 目前使用正则,易受页面改版影响;可结合 XPath/BeautifulSoup。
    • 对抓取文本统一做 .strip() 与字符集处理,避免脏数据。
  • 入库层
    • 当前用字符串拼接 SQL,建议改为参数化,避免注入与类型问题:
1
2
3
4
cursor.execute(
'INSERT INTO reptile_project_58(house_rent,pay_way,...) VALUES (:1,:2,...)',
(t1, t2, ...)
)
  • 连接复用与批量插入:避免每条关闭连接,使用 executemany 批量提交。
  • 唯一约束/重复控制:为关键字段建唯一索引,避免数据重复;或使用 MERGE 幂等写入。
  • 节流与风控
    • 已有 5–8 秒随机等待;可叠加每页间额外 sleep、失败快速跳过与后续重扫。
  • 日志与监控
    • print 改为 logging,输出到文件;记录失败 URL 以便补抓。

五八同城租房爬虫 项目总结
https://blog.pangcy.cn/2018/10/27/后端编程相关/python/python2基础/五八同城租房爬虫 项目总结/
作者
子洋
发布于
2018年10月27日
许可协议