大数据时代下,编写爬虫程序已经成为信息收集的必备技能;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同城二手房页码,查看网页源代码,我们可以发现我们需要的字段都包含在各个标签里。 

·············   

python爬取58同城职位信息 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为之后的存储预留空间。

python爬取58同城职位信息 58同城 爬虫_爬虫_02

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数组。

python爬取58同城职位信息 58同城 爬虫_爬虫_03

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限制,所以还需加深对反反爬虫的研究。(如果接下来还有需要及空闲时间,下一篇随笔可能就是相关内容,但是自己感觉大概率要鸽...)此外,多线程爬取、采集数据的增量处理等都是需要深入研究的内容。