從零開始學習python爬蟲(一):獲取58同城二手房信息

  大數據時代下,編寫爬蟲程序已經成爲信息收集的必備技能;python在數據挖掘方面具備極大優點且簡單易學,是新手入坑爬蟲程序編寫的極佳語言。html

  因爲在校期間本人主要應用java和matlab進行數據挖掘,所以藉助剛入職的學習期,簡單開發了一個最基本的python爬蟲獲取58同城二手房信息,一來是本身藉此練手python和爬蟲開發,二來是爬取的數據能夠實際用於本身以後的學習,也算是作個小小的預研吧。在兩個工做日的開發後,終於實現了用本身開發的爬蟲在58同城上爬取了1500條本地二手房數據。這篇隨筆將介紹這個簡單pyhon爬蟲的實現過程、及須要學習的知識和搭建的環境,但願能給一樣剛接觸python和爬蟲開發的學習者們一點點參考,存在不足請你們批評指正。前端


  開發前準備:java

   必備知識:python基本語法、web前端基本知識、數據庫基本知識python

   要編寫爬蟲程序,必要的python語法知識仍是不可少的。python簡單易學,對於用過其餘開發語言的開發者來講能很快上手(固然,只是簡單上手)。而後因爲咱們要從網頁上爬取,因此對web前端相關知識仍是須要大體瞭解下;最後咱們要將爬取的數據存入數據庫(本次開發採用oracle),因此數據庫基本知識也是不可少的。web

       搭建環境:python、oracle、pycharm正則表達式

  python和oracle的按照這裏再也不過多敘述,網上的資料已經有很是之多。本次開發python選擇的是2.7版本,開發工具我嘗試了pycharm和jupyter notebook,能夠說是各有千秋,實際開發中使用pycharm的人應該仍是更多的,一樣安裝配置過程在網上也能夠輕鬆找到。須要注意的是oracle字符集的清晰設定能夠避免以後的開發出現亂碼的坑。sql

  總體思路:chrome

  咱們的目標是從網頁中爬取須要的資料,因此整個開發思路能夠按此遞進展開:1.得到某一頁面的文本數據,截取須要的字段;2.將截取的字段進行整理,存放在數據庫中;3.實現爬取多頁或者某一列表的多條信息;4.組合完成一個能夠按頁依次爬取58同城二手房信息,並存入oracle的簡單爬蟲。思路明確後,接下來便介對其進行實現:數據庫


  第一步,先安裝好必要的模塊編程

  在cmd下進入python的scripts文件夾,按照BeautifulSoup四、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,pn一、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的特性還有待探索,若是能善用正則表達式和一些高效的模塊,想必開發能簡單很多。雖然本人很早就接觸博客園,可是這是我在博客園的第一篇文章,主要也是爲了記錄本身剛剛開始的工做生涯中對各類技術的探索,同時也但願能和你們一塊兒交流學習,爲一樣的萌新提供點參考,固然我更歡迎你們提出意見及批評指正。最後,本人爬取的數據僅用於我的的學習,毫不用於商業用途。

相關文章
相關標籤/搜索