如何高效地爬取链家的房源信息(一)
“Python實(shí)現(xiàn)的鏈家網(wǎng)站的爬蟲(chóng)第一部分。”
在之前的文章,以鏈家成都站為例,分析過(guò)鏈家網(wǎng)站數(shù)據(jù)的爬取,文章如下:
干貨!鏈家二手房數(shù)據(jù)抓取及內(nèi)容解析要點(diǎn)
但是,當(dāng)時(shí)沒(méi)有根據(jù)分析,將爬取實(shí)現(xiàn)。
本系列文將以鏈家南京站為例,使用Python實(shí)現(xiàn)鏈家二手房源信息的爬蟲(chóng),將數(shù)據(jù)爬取,并存入數(shù)據(jù)庫(kù)中,以便使用。
本文是第一部分,是整個(gè)爬取的基礎(chǔ),包括爬取邏輯、偽裝正常訪問(wèn)、數(shù)據(jù)庫(kù)設(shè)計(jì)及封裝、區(qū)域URL爬取四個(gè)部分。
01
—
爬取邏輯
本文爬取的地區(qū)站雖與之前分析的地區(qū)站不同,但二者的結(jié)構(gòu)是一樣的,之前分析的成果可以直接套用。
根據(jù)之前的分析成果,得到爬取流程如下:
第一步,找到爬取站點(diǎn)的地址,這里爬取的是南京站,為https://nj.lianjia.com/。
第二步,從二手房查詢頁(yè)面獲取大區(qū)信息,以便后續(xù)的查詢。
這樣的好處是可以分區(qū)查詢,避免單次數(shù)據(jù)太多,鏈家服務(wù)器無(wú)法返回全部?jī)?nèi)容,最終導(dǎo)致數(shù)據(jù)不全。
第二步,根據(jù)分區(qū)信息,獲取小區(qū)信息,存數(shù)據(jù)庫(kù),后續(xù)爬取操作,均以小區(qū)信息為起點(diǎn)。
第三步,根據(jù)各個(gè)小區(qū)信息,獲取該小區(qū)的所有在售房源信息,存數(shù)據(jù)庫(kù)。
第四步,根據(jù)各個(gè)小區(qū)信息,獲取該小區(qū)的所有成交房源信息,存數(shù)據(jù)庫(kù)。
確定了爬取流程,也就確定了程序的基本框架,接下來(lái)我們還需要準(zhǔn)備相關(guān)Python庫(kù),數(shù)據(jù)庫(kù)——sqlite3,以及分析網(wǎng)頁(yè)庫(kù)——BeautifulSoup。后者的安裝參考:
Windows下Python 3.6 安裝BeautifulSoup庫(kù)
02
—
偽裝正常訪問(wèn)
為避免被服務(wù)器發(fā)現(xiàn)爬蟲(chóng)行為,通常需要準(zhǔn)備一批User Agents,這里收集了一些UA,可以直接拿去使用:
hds = [{'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.12 Safari/535.11'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/44.0.2403.89 Chrome/44.0.2403.89 Safari/537.36'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1'}, \
? ? ? ?{'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11'}, \
? ? ? ?{'User-Agent': 'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11'}, \
? ? ? ?{'User-Agent': 'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11'}]
03
—
數(shù)據(jù)庫(kù)設(shè)計(jì)及封裝
由于房源數(shù)據(jù)量不小,使用文本方式保存明顯不便,因此,我們采用SQLite來(lái)進(jìn)行數(shù)據(jù)的存儲(chǔ)。這里,設(shè)計(jì)三個(gè)數(shù)據(jù)庫(kù),來(lái)保存不同的信息:
lianjia-xq.db,用來(lái)存儲(chǔ)小區(qū)信息,需要存儲(chǔ)URL、小區(qū)名稱、小區(qū)區(qū)域及建設(shè)年代等信息。
lianjia-cj.db,用來(lái)存儲(chǔ)小區(qū)成交二手房的信息,需要存儲(chǔ)鏈接、小區(qū)名稱、戶型、面積、朝向、裝修、電梯、簽約時(shí)間、簽約總價(jià)、樓層、年代樓型、來(lái)源、簽約單價(jià)、稅費(fèi)、地鐵、掛牌價(jià)、成交周期等信息。
lianjia-zs.db,用來(lái)存儲(chǔ)小區(qū)在售二手房的信息,需要存儲(chǔ)鏈接、房子描述、小區(qū)名稱、戶型、面積、朝向、裝修、電梯、樓層年代樓型、區(qū)域、關(guān)注、帶看、發(fā)布時(shí)間、稅費(fèi)、地鐵、限制、掛牌價(jià)、掛牌單價(jià)等信息。
這樣,基本上把網(wǎng)站上的描述點(diǎn)都給提取出來(lái)了。
同時(shí),我們對(duì)數(shù)據(jù)庫(kù)的操作進(jìn)行封裝以便操作,直接參考網(wǎng)上的代碼,并適當(dāng)修改,這里給出代碼:
class SQLiteWraper(object):
? ? """
? ? 數(shù)據(jù)庫(kù)的一個(gè)小封裝,更好的處理多線程寫(xiě)入
? ? """
? ? def __init__(self, path, command='', *args, **kwargs):
? ? ? ? self.lock = threading.RLock()? # 鎖
? ? ? ? self.path = path? # 數(shù)據(jù)庫(kù)連接參數(shù)
? ? ? ? if command != '':
? ? ? ? ? ? conn = self.get_conn()
? ? ? ? ? ? cu = conn.cursor()
? ? ? ? ? ? cu.execute(command)
? ? def get_conn(self):
? ? ? ? conn = sqlite3.connect(self.path)? # ,check_same_thread=False)
? ? ? ? conn.text_factory = str
? ? ? ? return conn
? ? def conn_close(self, conn=None):
? ? ? ? conn.close()
? ? def conn_trans(func):
? ? ? ? def connection(self, *args, **kwargs):
? ? ? ? ? ? self.lock.acquire()
? ? ? ? ? ? conn = self.get_conn()
? ? ? ? ? ? kwargs['conn'] = conn
? ? ? ? ? ? rs = func(self, *args, **kwargs)
? ? ? ? ? ? self.conn_close(conn)
? ? ? ? ? ? self.lock.release()
? ? ? ? ? ? return rs
? ? ? ? return connection
? ? @conn_trans
? ? def execute(self, command, method_flag=0, conn=None):
? ? ? ? cu = conn.cursor()
? ? ? ? try:
? ? ? ? ? ? if not method_flag:
? ? ? ? ? ? ? ? cu.execute(command)
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? cu.execute(command[0], command[1])
? ? ? ? ? ? conn.commit()
? ? ? ? except sqlite3.IntegrityError as e:
? ? ? ? ? ? # print e
? ? ? ? ? ? return -1
? ? ? ? except Exception as e:
? ? ? ? ? ? print(e)
? ? ? ? ? ? return -2
? ? ? ? return 0
? ? @conn_trans
? ? def fetchall(self, command="select name from xiaoqu", conn=None):
? ? ? ? cu = conn.cursor()
? ? ? ? lists = []
? ? ? ? try:
? ? ? ? ? ? cu.execute(command)
? ? ? ? ? ? lists = cu.fetchall()
? ? ? ? except Exception as e:
? ? ? ? ? ? print(e)
? ? ? ? ? ? pass
? ? ? ? return lists
同時(shí),封裝幾個(gè)具體的操作。
生成小區(qū)數(shù)據(jù)庫(kù)插入操作命令:
def gen_xiaoqu_insert_command(info_dict):
? ? """
? ? 生成小區(qū)數(shù)據(jù)庫(kù)插入命令
? ? """
? ? info_list = [ u'url',u'小區(qū)名稱', u'大區(qū)域', u'小區(qū)域', u'建造時(shí)間']?
? ? t = []
? ? for il in info_list:
? ? ? ? if il in info_dict:
? ? ? ? ? ? t.append(info_dict[il])
? ? ? ? else:
? ? ? ? ? ? t.append('')
? ? t = tuple(t)
? ? command = (r"insert into xiaoqu values(?,?,?,?,?)", t) #?,
? ? return command
生成成交記錄數(shù)據(jù)庫(kù)插入操作命令:
def gen_chengjiao_insert_command(info_dict):
? ? """
? ? 生成成交記錄數(shù)據(jù)庫(kù)插入命令
? ? """
? ? info_list = [u'鏈接',u'小區(qū)名稱',u'戶型',u'面積',u'朝向',u'裝修',u'電梯',u'簽約時(shí)間',u'簽約總價(jià)',u'樓層',u'年代樓型',u'來(lái)源',u'簽約單價(jià)',u'稅費(fèi)',u'地鐵',u'掛牌價(jià)',u'成交周期']
? ? t = []
? ? for il in info_list:
? ? ? ? if il in info_dict:
? ? ? ? ? ? t.append(info_dict[il])
? ? ? ? else:
? ? ? ? ? ? t.append('')
? ? t = tuple(t)
? ? command = (r"insert into chengjiao values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", t)
? ? return command
生成在售記錄數(shù)據(jù)庫(kù)插入操作命令:
def gen_zaishou_insert_command(info_dict):
? ? """
? ? 生成在售記錄數(shù)據(jù)庫(kù)插入命令
? ? """
? ? info_list = [u'鏈接',u'房子描述',u'小區(qū)名稱',u'戶型',u'面積',u'朝向',u'裝修',u'電梯',u'樓層年代樓型',u'區(qū)域',u'關(guān)注',u'帶看',u'發(fā)布時(shí)間',u'稅費(fèi)',u'地鐵',u'限制',u'掛牌價(jià)',u'掛牌單價(jià)']
? ? t = []
? ? for il in info_list:
? ? ? ? if il in info_dict:
? ? ? ? ? ? t.append(info_dict[il])
? ? ? ? else:
? ? ? ? ? ? t.append('')
? ? t = tuple(t)
? ? command = (r"insert into zaishou values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", t)
? ? return command
當(dāng)然,數(shù)據(jù)庫(kù)表需要?jiǎng)?chuàng)建:
command = "create table if not exists xiaoqu (url TEXT primary key UNIQUE,name TEXT, regionb TEXT, regions TEXT, year TEXT)"#
db_xq = SQLiteWraper('lianjia-xq.db', command)
command = "create table if not exists zaishou (href TEXT primary key UNIQUE, detail TEXT,xiaoqu TEXT, style TEXT, area TEXT, orientation TEXT, zhuangxiu TEXT, dianti TEXT, loucenniandai TEXT, quyu TEXT, guanzhu TEXT, daikan TEXT, fabushijian TEXT,shuifei TEXT,subway TEXT,xianzhi TEXT, total_price TEXT, unit_price TEXT)"
db_zs = SQLiteWraper('lianjia-zs.db', command)
command = "create table if not exists chengjiao (href TEXT primary key UNIQUE, name TEXT, style TEXT, area TEXT, orientation TEXT, zhuangxiu TEXT, dianti TEXT, sign_time TEXT, total_price TEXT, loucen TEXT, year TEXT,source TEXT, unit_price TEXT,shuifei TEXT, subway TEXT, guapaiprice TEXT, cycletime TEXT)"
db_cj = SQLiteWraper('lianjia-cj.db', command)
基礎(chǔ)準(zhǔn)備好了,接下來(lái)就是具體的爬取了。
04
—
區(qū)域URL爬取及異常處理
爬取大區(qū)域的URL:
def get_qu_url_spider():
? ? """
? ? 爬取大區(qū)信息
? ? """
? ? url = u"https://nj.lianjia.com/xiaoqu/"
? ? try:
? ? ? ? req = urllib.request.Request(url, headers=hds[random.randint(0, len(hds) - 1)])
? ? ? ? source_code = urllib.request.urlopen(req, timeout=5).read()
? ? ? ? plain_text = source_code.decode('utf-8');
? ? ? ? soup = BeautifulSoup(plain_text,"html.parser")
? ? except (urllib.request.HTTPError, urllib.request.URLError) as e:
? ? ? ? print(e)
? ? ? ? return
? ? except Exception as e:
? ? ? ? print(e)
? ? ? ? return
? ? d=soup.find('div', {'data-role':"ershoufang"}).findAll('a');
? ? for item in d:
? ? ? ? href=item['href'];
? ? ? ? region = item.contents[0];
? ? ? ? fullhref=u"https://nj.lianjia.com"+href;
? ? ? ? print(region+fullhref);
? ? ? ? regions.append(region)
? ? ? ? regionurls.append(fullhref)
爬取過(guò)程中如果異常,寫(xiě)入異常日志:
def exception_write(fun_name,url):
? ? """
? ? 寫(xiě)入異常信息到日志
? ? """
? ? lock.acquire()
? ? f = open('log.txt','a')
? ? line="%s %s\n" % (fun_name,url)
? ? f.write(line)
? ? f.close()
? ? lock.release()
爬取的URL不需要存入數(shù)據(jù)庫(kù),直接存到一個(gè)列表即可,然后遍歷,獲取各個(gè)大區(qū)域的小區(qū)信息。
在接下來(lái)將說(shuō)明如何爬取小區(qū)信息、在售二手房信息、歷史成交二手房信息,敬請(qǐng)期待。
長(zhǎng)按進(jìn)行關(guān)注。
總結(jié)
以上是生活随笔為你收集整理的如何高效地爬取链家的房源信息(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 邮Z速递物流,让用户密码在网络中遨游
- 下一篇: 如何高效地爬取链家的房源信息(二)