大數據時代下,編寫爬蟲程序已經成為信息收集的必備技能;python在數據挖掘方面具有極大優勢且簡單易學,是新手入坑爬蟲程序編寫的極佳語言。
由於在校期間本人主要應用java和matlab進行數據挖掘,因此借助剛入職的學習期,簡單開發了一個最基本的python爬蟲獲取58同城二手房信息,一來是自己借此練手python和爬蟲開發,二來是爬取的數據可以實際用於自己之后的學習,也算是做個小小的預研吧。在兩個工作日的開發后,終於實現了用自己開發的爬蟲在58同城上爬取了1500條本地二手房數據。這篇隨筆將介紹這個簡單pyhon爬蟲的實現過程、及需要學習的知識和搭建的環境,希望能給同樣剛接觸python和爬蟲開發的學習者們一點點參考,存在不足請大家批評指正。
開發前准備:
必備知識:python基本語法、web前端基本知識、數據庫基本知識
要編寫爬蟲程序,必要的python語法知識還是不可少的。python簡單易學,對於用過其他開發語言的開發者來說能很快上手(當然,只是簡單上手)。然后由於我們要從網頁上爬取,所以對web前端相關知識還是需要大致了解下;最后我們要將爬取的數據存入數據庫(本次開發采用oracle),所以數據庫基本知識也是不可少的。
搭建環境:python、oracle、pycharm
python和oracle的按照這里不再過多敘述,網上的資料已經有非常之多。本次開發python選擇的是2.7版本,開發工具我嘗試了pycharm和jupyter notebook,可以說是各有千秋,實際開發中使用pycharm的人應該還是更多的,同樣安裝配置過程在網上也可以輕松找到。需要注意的是oracle字符集的清晰設定可以避免之后的開發出現亂碼的坑。
整體思路:
我們的目標是從網頁中爬取需要的資料,所以整個開發思路可以按此遞進展開:1.獲得某一頁面的文本數據,截取需要的字段;2.將截取的字段進行整理,存放在數據庫中;3.實現爬取多頁或者某一列表的多條信息;4.組合完成一個可以按頁依次爬取58同城二手房信息,並存入oracle的簡單爬蟲。思路明確后,接下來便介對其進行實現:
第一步,先安裝好必要的模塊
在cmd下進入python的scripts文件夾,按照BeautifulSoup4、requests這兩個模塊。這兩個模塊都是python開發爬蟲的利器,BeautifulSoup4用於從網頁抓取數據,而requests則用於進行HTTP連接。具體操作如下。
cd C:\Python27\Scripts
pip install BeautifulSoup4
pip install requests
除此之外,還有pandas、sqlalchemy 、re需要安裝(同樣也是pip install),pandas用來整理數據、sqlalchemy 用來訪問數據庫、re用來實現正則表達式。安裝好后,我們new一個python文件,在文件開頭導入。
import requests
import pandas
import re
from bs4 import BeautifulSoup
from sqlalchemy import create_engine
設定os編碼可以避免之后數據庫的亂碼
import os
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
第二步,獲得某一頁面的文本數據
在爬取前,我們應該先明確爬取的原理。在google chrome打開開發者工具,訪問58同城二手房頁碼,查看網頁源代碼,我們可以發現我們需要的字段都包含在各個標簽里。
·············
我們的可以先將這些html代碼獲取下來,再進行篩選取得我們需要的字段。這里我定義了一個函數,傳入網頁的url,采用requests進行連接,然后在用BeautifulSoup獲取到文本的信息。
new_message_total = get_message_total('url')
df = pandas.DataFrame(new_message_total)
def get_message_total(message_uri):
res = requests.get(message_uri)
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text, 'html.parser')
通過觀察,我們發現每一套房屋信息中我們需要的大部分字段都包含在一個list-info的class里,於是我們便可以利用soup.select獲取所有list-info的信息,具體的用法可以去看看BeautifulSoup的相關文檔,我們輸出了soup.select('.list-info')的數組長度,即每一頁出現房屋信息的次數;同時定義一個message_total為之后的存儲預留空間。
def get_message_total(message_uri):
res = requests.get(message_uri)
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text, 'html.parser')
num = len(soup.select('.list-info'))
message_total = []
print(num)
第三步,將截取的字段進行整理
這里建立了一個循環去獲取每個soup.select('.list-info')數組的元素,即每個房屋信息,如soup.select('.list-info')[0]即第一條房屋信息。接下來再用soup.select獲取每個需要的字段,然后對字段進行了簡單處理(字段去掉空格換行,面積價格去單位),這里我幾乎沒有用到正則表達式。取得的字段我們存放在一個result里,在每次循環結束一並存放到message_total中,在獲取全部的數據后我們將其作為函數的返回。
for i in range(0, num):
message = soup.select('.list-info')[i]
pricetext = soup.select('.price')[i]
title = message.select('a')[0].text.replace('\t', '').replace('\n', '').replace(' ', '')
housetype = message.select('span')[0].text.replace('\t', '').replace('\n', '').replace(' ', '')
areatext = message.select('span')[1].text.replace('\t', '').replace('\n', '').replace(' ', '')
area=re.findall(r"\d+\.?\d*",areatext)[0]
orientation = message.select('span')[2].text.replace('\t', '').replace('\n', '').replace(' ', '')
floor = message.select('span')[3].text.replace('\t', '').replace('\n', '').replace(' ', '')
estate = message.select('a')[1].text.replace('\t', '').replace('\n', '').replace(' ', '')
region = message.select('a')[2].text.replace('\t', '').replace('\n', '').replace(' ', '')
position = message.select('a')[3].text.replace('\t', '').replace('\n', '').replace(' ', '')
totalpricetext = pricetext.select('p')[0].text.replace('\t', '').replace('\n', '').replace(' ', '')
totalprice=re.findall(r"\d+\.?\d*",totalpricetext)[0]
unitpricetext= pricetext.select('p')[1].text.replace('\t', '').replace('\n', '').replace(' ', '')
unitprice=re.findall(r"\d+\.?\d*",unitpricetext)[0]
result = {}
result['title'] = title
result['housetype'] = housetype
result['area'] = area
result['orientation'] = orientation
result['floor'] = floor
result['estate'] = estate
result['region'] = region
result['position'] = position
result['totalprice'] = totalprice
result['unitprice'] = unitprice
message_total.append(result)
return message_total
第四步,將數據存入數據庫中 我們獲取返回的數組后,采用pandas將其進行整理,然后建立與數據庫的連接,采用to_sql方法將整個dateframe存入數據庫。to_sql方法有多參數可以設定,很重要的一個是if_exists,如果設為'append'則表示表如果存在則進行插入操作。
new_message_total = get_message_total(url')
df = pandas.DataFrame(new_message_total)
print(df)
db_engine=create_engine('oracle://name:password@url/databasename')
df.to_sql(name=tablename, con=db_engine, if_exists='append', index=False)
第五步,實現實現爬取多頁或者某一列表的多條信息
多頁爬取的實現比較簡單,將url中關於頁碼的參數進行賦值(在這里為/ershoufang/pn,pn1、pn2....表示第一頁第二頁)存放到一個數組中,接着循環調用get_message_total函數去查詢整個url數組即可。而要爬取某一頁中的每個房產信息的詳細情況則(多層次的爬取)比較麻煩。一個方法是采用soup.select獲取相應<a>標簽中記錄房產信息詳情的地址,然后將其url記錄,用和多頁爬取相同的方法進行爬取。第二種方法是在開發者工具-Network-js中進行尋找可能存放url的js文件,我們發現這邊的query_list中涵蓋了多個房產信息詳情的地址,我們將其獲取去掉開頭的函數名和括號及結尾的括號,獲得一個json字符串,再將json字符串中每個url獲取出來進行記錄,然后在用get_message_total函數去查詢整個url數組。
res = requests.get(
'http://api.fang.58.com/aurora/query_list.............')
res.encoding = 'utf-8'
num = 0
jd = json.loads(res.text.lstrip('jQuery112408648859133127407_1528161168333(').rstrip(')\''))
for ent in jd['data']['houseList']:
print num
num = num + 1
res = requests.get(jd['data']['houseList'][0]['url'])
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text, 'html.parser')
print(soup.text)
第六步,整合
這里我只將爬取單個頁面的源碼全部粘貼出來,不將所有代碼貼出的原因是同城的反爬蟲機制使得目前的多列表查詢或者多頁查詢形同虛設,且思路明確后多頁查詢和多列表查詢的第一種方法只是在重復之前的工作,而多列表查詢第二種方法的關鍵代碼也已經貼出。有需要的同學改下數據庫參數便可用於爬取58二手房信息,將中間的獲取規則部分進行修改后(可以用正則表達式,會簡單很多)也可以用於其他場景。
# encoding: utf-8
import requests
import pandas
import re
from bs4 import BeautifulSoup
from sqlalchemy import create_engine
import os
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
def get_message_total(message_uri):
res = requests.get(message_uri)
res.encoding = 'utf-8'
soup = BeautifulSoup(res.text, 'html.parser')
num = len(soup.select('.list-info'))
message_total = []
print(num)
for i in range(0, num):
message = soup.select('.list-info')[i]
pricetext = soup.select('.price')[i]
title = message.select('a')[0].text.replace('\t', '').replace('\n', '').replace(' ', '')
housetype = message.select('span')[0].text.replace('\t', '').replace('\n', '').replace(' ', '')
areatext = message.select('span')[1].text.replace('\t', '').replace('\n', '').replace(' ', '')
area=re.findall(r"\d+\.?\d*",areatext)[0]
orientation = message.select('span')[2].text.replace('\t', '').replace('\n', '').replace(' ', '')
floor = message.select('span')[3].text.replace('\t', '').replace('\n', '').replace(' ', '')
estate = message.select('a')[1].text.replace('\t', '').replace('\n', '').replace(' ', '')
region = message.select('a')[2].text.replace('\t', '').replace('\n', '').replace(' ', '')
position = message.select('a')[3].text.replace('\t', '').replace('\n', '').replace(' ', '')
totalpricetext = pricetext.select('p')[0].text.replace('\t', '').replace('\n', '').replace(' ', '')
totalprice=re.findall(r"\d+\.?\d*",totalpricetext)[0]
unitpricetext= pricetext.select('p')[1].text.replace('\t', '').replace('\n', '').replace(' ', '')
unitprice=re.findall(r"\d+\.?\d*",unitpricetext)[0]
result = {}
result['title'] = title
result['housetype'] = housetype
result['area'] = area
result['orientation'] = orientation
result['floor'] = floor
result['estate'] = estate
result['region'] = region
result['position'] = position
result['totalprice'] = totalprice
result['unitprice'] = unitprice
message_total.append(result)
return message_total
new_message_total = get_message_total(url')
df = pandas.DataFrame(new_message_total)
print(df)
db_engine=create_engine('oracle://name:password@url/databasename')
df.to_sql(name=tablename, con=db_engine, if_exists='append', index=False)
下一步:目前的爬蟲程序還只是一個簡單的小demo,首要解決的還是各大網站的反爬蟲限制問題。比如58同城爬取數據中,每爬取幾十條數據便會要求輸驗證碼,這使得我們的多頁爬取失去了意義(每爬取一兩頁就要手動輸入驗證碼),而安居客則會直接檢測爬蟲程序(之后可能會采用偽裝成瀏覽器的方法進行解決)。因為我們不可能像一些平台那樣采用ip池的方法避免ip限制,所以還需加深對反反爬蟲的研究。(如果接下來還有需要及空閑時間,下一篇隨筆可能就是相關內容,但是自己感覺大概率要鴿...)此外,多線程爬取、采集數據的增量處理等都是需要深入研究的內容。
感悟:從熟悉python語法到開發完成花了兩個工作日,很大一部分時間都用在解決各種坑上(搭建環境、更改數據庫字符集等),這段時間的學習我確實感受到python的簡單好用,但是我目前對python還停留在初步認識的階段,很多要用到的知識點都是遇到了再去查;因為時間緊湊,目前只是按照之前的編程思路運用python這個工具將自己的目標實現,對python的特性還有待探索,如果能善用正則表達式和一些高效的模塊,想必開發能簡單不少。雖然本人很早就接觸博客園,但是這是我在博客園的第一篇文章,主要也是為了記錄自己剛剛開始的工作生涯中對各種技術的探索,同時也希望能和大家一起交流學習,為同樣的萌新提供點參考,當然我更歡迎大家提出意見及批評指正。最后,本人爬取的數據僅用於個人的學習,絕不用於商業用途。