Python玩转爬虫第一弹

爬虫相关操作步骤:

一、确定需要爬取网页信息的url(统一资源定位符),爬取网页时需要根据url作为地址索引,得到所需采集的信息。

二、在浏览器中打开所需爬取的网页,找到所需要采集信息的位置,并通过查看网页源代码的方式找到采集信息在网页代码中的层次,根据此为依据获取信息。

三、使用Python抓取网页信息的基本思路如一二所示,当然,完整的爬虫程序还应当涉及网页源码的缓存、采集信息的储存等一系列操作,这些操作会在后面的文档中给出步骤。

Python用于爬虫的库有很多,同时还有Scrapy框架也是用于爬虫程序的编写的,在下面的介绍中我会先对一些库进行介绍,在介绍完库及其相关函数后,再对scrapy框架进行更深入的介绍。

接下来介绍几个常用的用于Python爬虫程序编写的库和包以及库中一些相关函数的用法和含义。

第一步,先介绍如何使用urllib, urllib2, re, bs4, lxml等库进行静态网页的源代码提取和解析操作。

1 import urllib #导入urllib库

2 import urllib2 #导入urllib2库

注意:使用python导入相关库之前,必须保证在Python的安装目录site-packges中已经安装了相应的库,可以通过上面的语句来进行测试,运行语句,看是否报错。

在这里,推荐安装Anaconda集成包,因为在这个Python的编译环境下,已经为我们提前安装好了很多常用的库,这样就免去了在网上寻找库资源和安装库的步骤。当然,如果在Anaconda中也不存在的库,还是得需要按照“pip install文件名”的格式进行安装。

4 print response.read() #使用read方法将得到的源代码打印出来

注:在这里,我们先对urlopen函数进行一个简单的介绍。

函数格式:urlopen(url, data, timeout)

其中,第一个参数url即为URL(统一资源定位符,通常可以理解为我们常说的网址),第二个参数data是访问URL时要传送的数据,第三个参数timeout是设置的超时时间。从我们上面给出的代码可以看到,第二和第三个参数都是可以不传送的,那么此时data默认为None,timeout则默认为socket._GLOBAL_DEFAULT_TIMEOUT,但第一个参数url是必须传送的。

上面给出的代码是最简单的提取网页源代码的方式。接下来,我们尝试构建Request,通过构建一个Request类的实例,在该实例中我们给定需要传入的url, data等内容,然后将这个request请求传入到urlopen函数中。

1 import urllib

2 import urllib2

4 response = urllib2.urlopen(request) #现在的urlopen函数传入上一步构建的request实例

5 print response.read()

接着,考虑第二个参数data的设置和传输。很多网页都是需要提供数据输入的,比如说最常见的登录注册时,就需要用户提供用户名和密码,才能进入相应的网页中。这里,介绍一下数据传送的两种方式:POST和GET数据传送。

POST数据传送方式:

1 import urllib

2 import urllib2

3 values = {“username”: “xxxx”, “password”:“xxx”} #字典数据格式,用来给data参数赋值

4 data = urllib2.urlencoed(values) #使用urlencode函数将上面的values以url编码

6 response = urllib2.urlopen(request)

7 print response.read()

GET数据传送方式:#可以直接将参数写在网址上面,即将url和data参数进行整合,写在一起,然后传送到Request类中,用于构建一个带有url和data参数的request实例。

1 import urllib

2 import urllib2

3 values[‘username’] = “xxx”

4 values[‘password’] = “xxx”

5 data = urllib2.urlencode(values) #对values进行编码

6 url = “http://passport.csdn.net/account/login”

7 geturl = url + “?” + data #将url和data参数利用正则表达式整合在一起

8 request = urllib2.Request(geturl) #根据geturl来构建request实例

9 response = urllib2.urlopen(request)

10 print response.read()

注:正则表达式将在以后的内容中进行介绍,要实现网页数据的抓取,正则表达式部分的学习是极其重要的。

相较之下,get数据传送方式将data参数直接写在网址上,安全性要显得更差一些。

上面的介绍部分已经可以实现简单的爬虫操作,但还远远不够,对于大部分网页而言,我们还需要进行进一步的深入探讨。

第二步,介绍网页响应过程,网页显示的过程相当于多次请求的过程,先请求HTML文件,然后加载JS、CSS等文件,逐步实现网页架构,当然,这些操作都由浏览器进行完成,我们不需深究,只需要了解大概的工作过程以及一些简单的网页源代码的结构。在对网页源代码有一定的了解之后,我们编写的爬虫程序也应当根据网页源代码的特性,实现各类模拟,以达到我们抓取网页数据的目的。

在该部分,我首先要引入请求身份agent的概念。打开网页,查看网页结构时,我们通常会看到各种各样的信息,其中请求身份agent就是一个重要的组成部分。如果缺少agent部分,服务器可能不会响应,因此我们需要在程序中加入headers部分,并在其中设置agent。下面的代码部分就来展示如何设置agent。

1 import urllib

2 import urllib2

4 user_agent = ‘Mozilla/4.0 (compatible;MSIE 5.5; Windows NT)’

5 values[‘username’] = “xxx”

6 values[‘password’] = “xxx”

7 data = urllib2.encode(values)

8 headers = {“User_Agent”: user_agent} #字典数据格式

9 request = urllib2.Request(url, data, headers)

10 response = urllib2.urlopen(request)

11 print response.read()

注:有些情况下,设置上述的代理请求可能也无法得到网页的响应,这时候就需要用到对付“反盗链”的方式,对付防盗链,服务器会识别headers中的referer是不是它自己,如果不是,有些服务器不会响应,所以我们还可以在headers中加入referer,如下:

1 headers = {’User_Agent’: ‘Mozilla/4.0(compatible; MSIE 5.5; Windows NT)’, ‘Referer’: ‘http://www.zhihu.com/articles’}

第三步,Proxy(代理)的设置

Urllib2默认会使用变量http_proxy来设置HTTP Proxy。若一个网站会检测某一段时间某个IP的访问次数,如果访问次数过多,它会禁止你的访问。所以可以设置一些代理服务器来帮助工作,每隔一段时间换一个代理。

1 import urllib

2 import urllib2

3 enable_proxy = True

4 proxy_handler =urllib2.ProxyHandler({“http”: ‘http//some-proxy.com:8080’}) #设置proxy代理

5 null_proxy_handler =urllib2.ProxyHandler({}) #设置空的proxy代理

6 if enable_proxy:

7 opener= urllib2.bulid_opener(proxy_handler)

8 else:

9 opener= urllib2.build_opener(null_proxy_handler)

10 urllib2.install_opener(opener)

第四步,设置Timeout

Timeout参数是urlopen中的参数之一,可以不用传递,但也可以通过设置等待超时时间来解决一些网站实在响应过慢的问题。

1 import urllib

2 import urllib2

下面开始介绍异常处理

Python抛出异常与Java类似,通常我们在用爬虫程序时会遇到的异常处理都是涉及到的URLError还有HTTPError,下面就是对它们的一些相关处理工作。

首先介绍URLErrror,URLErrror可能产生的原因包括:

网络无法连接,本机无法上网;

连接不到特定的服务器;

服务器不存在。

与Java类似,我们使用try-except语句来捕获异常并抛出异常。

1 import urllib

2 import urllib2

4 try:

5 urllib2.urlopen(request) #执行urlopen函数,打开传递的统一资源定位符位置

6 except urllib2.URLError, e: #捕获异常

7 printe.reason #若存在异常,则将出现异常的原因打出

接着介绍HTTPError

首先需要明确一个概念:HTTPError是URLError的子类,当使用urlopen方法时,实际上是发出了一个请求,这时服务器上都会对应一个应答对象response,其中包含一个数字“状态码”。而urllib2则将会对response进行处理,当然,也会存在一些处理不了的情况,这时候urlopen就会产生一个HTTPError,对应相应的状态码,HTTP状态码表示HTTP协议所返回的响应的状态。这里对相关的状态码就不进行一一介绍了。

HTTPError实例产生后会有一个code属性,这应当是服务器发送的相关错误号。例:

1 import urllib

2 import urllib2

4 try:

5 urllib2.urlopen(request) #执行urlopen函数,可以得到统一资源定位符的网页源代码

6 except urllib2.HTTPError, e:

7 printe.code #打印服务器发送的相关错误号

8 printe.reason #打印服务器发送错误的原因

另外,还可以加入hasattr属性提前对属性进行判断:

1 import urllib

2 import urllib2

4 try:

5 urllib2.urlopen(request)

6 except urllib2.URLError, e:

7 ifhasattr(e, “reason”): #引入hasattr属性对属性进行判断

8 printe.reason #打印服务器发送错误的原因

9 else:

10 print“ok”

到目前为止,我们对整个python爬虫程序的框架有了大致的了解,但至今我们还只能抓取到网页的整个源代码。而通常情况下,我们是需要从源代码中找到所需获取信息的位置的,当然,由javascript编写的网页需要用其它的方法获取网页信息(也就是通常所说的动态网页,这里有关动态网页信息的获取将会在之后的文档中给出详细介绍。)

接下来的内容就是有关如何从网页指定位置获取信息的步骤。

当前使用得比较多的信息匹配方法主要有三种:分别是正则表达式、bs4库中的BeautifulSoup方法以及lxml库中所包含的方法。鉴于三种方法都有其优劣性,这里将三种方法的相关操作都写在以下的文档中。

注:相关库都已经在Anaconda中实现了安装,若本机中没有安装相关库的话,则需对应Python版本,下载库文件,使用“pip install文件名”的方式进行库的安装。

正则表达式是指对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、以及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

在很多编程语言中,正则表达式都是非常强大的用于字符串匹配的方式。但正则表达式也存在着不够灵活、理解和构造过程较为困难的问题。在接下来的网页爬取实例中,会体现正则表达式的一些具体应用。

BeautifulSoup:是一个非常流行的Python模块,它的操作过程较为简单,同时也更容易被理解,但由于BeautifulSoup是由纯Python编写的库,因此在爬取网页的速度方面是不如正则表达式和Lxml库的。

BeautifulSoup的工作流程大致如下:

第一步:将已下载的HTML内容解析为soup文档,在这一步中值得注意的是,因为大多数网页都并不具备良好的HTML格式,因此BeautifulSoup会对HTML内容的实际格式进行确定,并将缺失的HTML格式补充完整。

第二步:在BeautifulSoup将HTML格式补全后,就可以使用find()和find_all()方法来定位需要的元素。

在这里就不给出具体的代码内容了,会在抓取网页的实例中给出BeautifulSoup相关方法和操作流程的具体注释。

Lxml是基于libxml12这一XML解析库的Python封装。之前有提到过BeautifulSoup库是由纯Python语言编写的库,因此在抓取网页的速度方面会不如正则表达式。而Lxml模块则是使用C语言进行的编写,解析速度比BeautifulSoup更快。

Lxml的操作步骤大致如下:

第一步:将已下载的HTML解析为统一格式。

第二步:在解析完输入内容之后,进入选择元素的步骤。相较BeautifulSoup库,Lxml有几种不同的方法可以实现元素的选择,如XPath选择器和类似BeautifulSoup的find()方法,还有CSS、jQuery选择器的方法进行元素的选择操作,这种方法更加简洁。

同样的,在这里也不给出具体的代码内容,在抓取网页的实例中会对正则表达式、BeautifulSoup以及Lxml库的操作给出具体的注释。

在介绍完这些网页抓取、解析以及元素的选择之后,下面对网页缓存及数据存放技术进行相关操作的解读。

缓存技术是指:我们需要将所抓取到的网页缓存到本地或者存储到CSV表格及数据库中。同时,要考虑的是如何将爬取得到的数据进行命名,以便对数据进行后续处理。

如果将所爬取的网页内容缓存至本机的文件系统中,为了保证文件路径的安全性,我们就需要限制文件路径只能包含数字、字母和基本符号,并将其他字符替换为下划线。实现示例如下:

1 import re #导入re库,该库是在使用正则表达式时需要导入的库

2 url = ‘http://baidu.com’

3 re.sub(‘[^/0-9a-zA-ZA\-.,;_]’, ‘_’,url) #使得url值只包含数字、字母和基本符号,将其它字符替换为下划线

但是,一般情况下,很少使用文件系统进行数据的缓存,即使使用该缓存技术的速度通常来说是最快的,因其存在命名的限制和清理过期数据的问题,通常来说使用更多的还是利用数据库对所下载的网页数据进行缓存。特别是针对数据结构复杂及分布式处理的爬虫程序而言,非关系型数据库虽然会损失一些速度,但在文件命名、索引和存储能力方面要更为优秀,因此,下面介绍使用非关系型数据库MongoDB实现数据的缓存及索引操作。

注:MongoDB实现了诸如B+树这类的算法进行索引操作,因此我们无须再对所缓存的网页编写索引算法,直接使用数据库会降低爬取和使用数据的复杂程度。

选择NoSQL的原因是基于在爬取时,我们通常会需要缓存大量数据,但又无须任何复杂的连接操作,同时,NoSQL数据库比关系型数据库更容易扩展。

注:若需要使用数据库,并使用python进行数据库的操作和调用,不仅需要在本地安装好对应的数据库,还应额外安装其Python封装库。如安装了Mysql数据库后,还需要安装Python封装库Mysqldb,同样的,安装了MongoDB数据库后,还应当安装其Python封装库pymongo。

下面构建的MongoCache类大致实现了数据的缓存及定期清理过期数据的功能。

#_*_ coding: utf-8 _*_

fromdatetimeimportdatetime,timedelta

frompymongoimportMongoClient

classMongoCache:

def__init__(self, client =None, expires = timedelta(days=30)):

self.clinet= MongoCient('localhost',27107)

self.db =client.cache

self.db.webpage.create_index('timestamp',expireAfterSeconds= expires.total_seconds())

def__getitem__(self, url):

"""在这个URL地址中加载值"""

record =self.db.webpage.find_one({'_id': url})#找到对应该url的记录

ifrecord:

returnrecord['result']#如果数据库中存在对应该url值的记录,则返回其结果,若不存在,则返回该url不存在

else:

raiseKeyError(url +'does not exist')

def__setitem__(self,url, result):

"""保存数据并实现数据的定期删除"""

record = {'result':result,'timestamp': datetime.utcnow()}

self.db.webpage.update({'_id': url}, {'$set': record}), upsert =True)

爬虫还涉及到并发下载等功能,在这里先不对其进行介绍。并发下载进行多线程、多线程的爬虫程序编写,对爬取速度进行大幅度的提升。

下面,介绍一个使用爬虫获取一个具体网页数据的实例。(一般会使用火狐firefox浏览器进行网页元素的审查)

# -*- coding: UTF-8 -*-

#导入python库

importurllib

importurllib2

importre

importMySQLdb

importjson

#定义爬虫类

classcrawl1:

defgetHtml(self,url=None):

#代理

user_agent="Mozilla/5.0 (Windows NT 6.1;WOW64; rv:40.0) Gecko/20100101 Firefox/40.0"

header={"User-Agent":user_agent}

request=urllib2.Request(url,headers=header)

response=urllib2.urlopen(request)

html=response.read()

returnhtml

defgetContent(self,html,reg):

content=re.findall(html, reg, re.S)

returncontent

#连接数据库mysql

defconnectDB(self):

host="192.168.85.21"

dbName="test1"

user="root"

password="123456"

#此处添加charset='utf8'是为了在数据库中显示中文,此编码必须与数据库的编码一致

db=MySQLdb.connect(host,user,password,dbName,charset='utf8')

returndb

cursorDB=db.cursor()

returncursorDB

#创建表,SQL语言。CREATE TABLE IF NOT EXISTS表示:表createTableName不存在时就创建

defcreatTable(self,createTableName):

createTableSql="CREATE TABLE IF NOT EXISTS "+ createTableName+"(time VARCHAR(40),titleVARCHAR(100),text VARCHAR(40),clicksVARCHAR(10))"

DB_create=self.connectDB()

cursor_create=DB_create.cursor()

cursor_create.execute(createTableSql)

DB_create.close()

print'creat table '+createTableName+' successfully'

returncreateTableName

#数据插入表中

definserttable(self,insertTable,insertTime,insertTitle,insertText,insertClicks):

insertContentSql="INSERT INTO "+insertTable+"(time,title,text,clicks)VALUES(%s,%s,%s,%s)"

# insertContentSql="INSERT INTO"+insertTable+"(time,title,text,clicks)VALUES("+insertTime+", "+insertTitle+" , "+insertText+" ,"+insertClicks+")"

DB_insert=self.connectDB()

cursor_insert=DB_insert.cursor()

cursor_insert.execute(insertContentSql,(insertTime,insertTitle,insertText,insertClicks))

DB_insert.commit()

DB_insert.close()

print'inert contents to '+insertTable+' successfully'

url="http://baoliao.hb.qq.com/api/report/NewIndexReportsList/cityid/18/num/20/pageno/1?callback=jQuery183019859437816181613_1440723895018&_=1440723895472"

#正则表达式,获取js,时间,标题,文本内容,点击量(浏览次数)

reg_jason=r'.*?jQuery.*?\((.*)\)'

reg_time=r'.*?"create_time":"(.*?)"'

reg_title=r'.*?"title":"(.*?)".*?'

reg_text=r'.*?"content":"(.*?)".*?'

reg_clicks=r'.*?"counter_clicks":"(.*?)"'

#实例化crawl()对象

crawl=crawl1()

html=crawl.getHtml(url)

html_jason=re.findall(reg_jason,html, re.S)

html_need=json.loads(html_jason[])

printlen(html_need)

printlen(html_need['data']['list'])

table=crawl.creatTable('yh1')

foriinrange(len(html_need['data']['list'])):

creatTime=html_need['data']['list'][i]['create_time']

title=html_need['data']['list'][i]['title']

content=html_need['data']['list'][i]['content']

clicks=html_need['data']['list'][i]['counter_clicks']

crawl.inserttable(table,creatTime,title,content,clicks)

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180718G1RB0J00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券