从零开始学习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的特性还有待探索,若是能善用正则表达式和一些高效的模块,想必开发能简单很多。虽然本人很早就接触博客园,可是这是我在博客园的第一篇文章,主要也是为了记录本身刚刚开始的工做生涯中对各类技术的探索,同时也但愿能和你们一块儿交流学习,为一样的萌新提供点参考,固然我更欢迎你们提出意见及批评指正。最后,本人爬取的数据仅用于我的的学习,毫不用于商业用途。

相关文章
相关标签/搜索