Python网络爬虫阶段总结

来源:互联网 发布:中航工业301所待遇知乎 编辑:程序博客网 时间:2024/06/06 10:55
学习python爬虫有一个月了,现在将学习的东西和遇到的问题做一个阶段总结,以作复习备用,另对于python爬虫感兴趣的,如果能帮到你们少走些弯路,那也是极好的。闲话少说,下面直接上干货:

 

Python学习网络爬虫主要分3个大的版块:抓取分析存储

另外,比较常用的爬虫框架Scrapy,这里最后也详细介绍一下。

 

当我们在浏览器中输入一个url后回车,后台会发生什么?

简单来说这段过程发生了以下四个步骤:

· 查找域名对应的IP地址。

· IP对应的服务器发送请求。

· 服务器响应请求,发回网页内容。

· 浏览器解析网页内容。

网络爬虫要做的,简单来说,就是实现浏览器的功能。通过指定url,直接返回给用户所需要的数据,而不需要一步步人工去操纵浏览器获取。

抓取

这一步,你要明确要得到的内容是什么?是HTML源码,还是Json格式的字符串等。

1. 最基本的抓取

抓取大多数情况属于get请求,即直接从对方服务器上获取数据。

首先,Python中自带urllib及urllib2这两个模块,基本上能满足一般的页面抓取。另外,requests也是非常有用的包,与此类似的,还有httplib2等等。

Requests:

import requests

response = requests.get(url)

content = requests.get(url).content

print "response headers:", response.headers

print "content:", content

Urllib2:标签整齐,清晰,看着比较舒服,以后可以用它,便于阅读HTML

import urllib2

response = urllib2.urlopen(url)

content = urllib2.urlopen(url).read()

print "response headers:", response.headers

print "content:", content

Httplib2:

import httplib2

http = httplib2.Http()

response_headers, content = http.request(url, 'GET')

print "response headers:", response_headers

print "content:", content

此外,对于带有查询字段的url,get请求一般会将来请求的数据附在url之后,以?分割url和传输数据,多个参数用&连接。

data = {'data1':'XXXXX', 'data2':'XXXXX'}

Requests:data为dict,json

import requests

response = requests.get(url=url, params=data)

Urllib2:data为string

import urllib, urllib2    

data = urllib.urlencode(data)

full_url = url+'?'+data

response = urllib2.urlopen(full_url)



2. 对于登陆情况的处理

2.1 使用表单登陆

这种情况属于post请求,即先向服务器发送表单数据,服务器再将返回的cookie存入本地。

data = {'data1':'XXXXX', 'data2':'XXXXX'}

Requests:data为dict,json

import requests

response = requests.post(url=url, data=data)

Urllib2:data为string

import urllib, urllib2    

data = urllib.urlencode(data)

req = urllib2.Request(url=url, data=data)

response = urllib2.urlopen(req)

2.2 使用cookie登陆

使用cookie登陆,服务器会认为你是一个已登陆的用户,所以就会返回给你一个已登陆的内容。因此,需要验证码的情况可以使用带验证码登陆的cookie解决。

import requests

requests_session = requests.session()

response = requests_session.post(url=url_login, data=data)

若存在验证码,此时采用response = requests_session.post(url=url_login, data=data)是不行的,做法应该如下:

response_captcha = requests_session.get(url=url_login, cookies=cookies)

response1 = requests.get(url_login) # 未登陆

response2 = requests_session.get(url_login) # 已登陆,因为之前拿到了Response Cookie!

response3 = requests_session.get(url_results) # 已登陆,因为之前拿到了Response Cookie!



3. 对于反爬虫机制的处理

3.1 使用代理

适用情况:限制IP地址情况,也可解决由于“频繁点击”而需要输入验证码登陆的情况

这种情况最好的办法就是维护一个代理IP池,网上有很多免费的代理IP,良莠不齐,可以通过筛选找到能用的。对于“频繁点击”的情况,我们还可以通过限制爬虫访问网站的频率来避免被网站禁掉

关键代码,如下几行:

proxies = {'http':'http://XX.XX.XX.XX:XXXX'}

Requests:

import requests

response = requests.get(url=url, proxies=proxies)

Urllib2:

import urllib2

proxy_support = urllib2.ProxyHandler(proxies)

opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)

urllib2.install_opener(opener) # 安装opener,此后调用urlopen()时都会使用安装过的opener对象

response = urllib2.urlopen(url)

这在某些情况下比较有用,比如IP被封了,或者比如IP访问的次数受到限制等等。此时,可以在middlewares.py中通过类对代理IP进行封装,详细代码如下:

class ProxyMiddleware(object):

    def process_request(self, request, spider):

    proxy = random.choice(PROXIES)

    if proxy['user_pass'] is not None:

request.meta['proxy'] = "http://%s" % proxy['ip_port']

encoded_user_pass = base64.encodestring(proxy['user_pass'])

request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_pass

print "**************ProxyMiddleware have pass************" + proxy['ip_port']

    else:

print "**************ProxyMiddleware no pass************" + proxy['ip_port']

request.meta['proxy'] = "http://%s" % proxy['ip_port']

 

PROXIES = [

        {'ip_port': '218.4.101.130:83', 'user_pass': ''},

        {'ip_port': '113.121.47.97:808', 'user_pass': ''},

        {'ip_port': '112.235.20.223:80', 'user_pass': ''},

        {'ip_port': '27.151.30.68:808', 'user_pass': ''},

        {'ip_port': '175.155.25.50:808', 'user_pass': ''},

        {'ip_port': '222.85.50.207:808', 'user_pass': ''},

        {'ip_port': '116.255.153.137:8082', 'user_pass': ''},

        {'ip_port': '119.5.0.26:808', 'user_pass': ''},

        {'ip_port': '183.32.88.223:808', 'user_pass': ''},

        {'ip_port': '180.76.154.5:8888', 'user_pass': ''},

        {'ip_port': '221.229.44.174:808', 'user_pass': ''},

        {'ip_port': '27.151.30.68:808', 'user_pass': ''},

        {'ip_port': '60.178.86.7:808', 'user_pass': ''},

        {'ip_port': '58.243.104.149:8998', 'user_pass': ''},

        {'ip_port': '120.27.49.85:8090', 'user_pass': ''},

]

注意,由于代理IP一般都有时效性,需要找到能用的代理IP将上面ip_port关键字对应的值替换下来。

3.2 时间设置

适用情况:限制频率情况。

Requests,Urllib2都可以使用time库的sleep()函数:

import time

time.sleep(1)

3.3 伪装成浏览器,或者反“反盗链”

有些网站会检查你是在使用真的浏览器访问,还是机器自动访问的。这种情况,加上User-Agent,表明你是浏览器访问即可。有时还会检查是否带Referer信息还会检查你的Referer是否合法,一般再加上Referer。

headers = {'User-Agent':'XXXXX'} # 伪装成浏览器访问,适用于拒绝爬虫的网站

headers = {'Referer':'XXXXX'}

headers = {'User-Agent':'XXXXX', 'Referer':'XXXXX'}

Requests:

response = requests.get(url=url, headers=headers)

Urllib2:

import urllib, urllib2   

req = urllib2.Request(url=url, headers=headers)

response = urllib2.urlopen(req)

详细的,可以middlewares.py中通过类对代理浏览器进行封装

from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware

class RotateUserAgentMiddleware(UserAgentMiddleware):

    def __init__(self, user_agent=''):

        self.user_agent = user_agent

 

    def process_request(self, request, spider):

        ua = random.choice(self.user_agent_list)

        if ua:

            #print ua, '-----------------yyyyyyyyyyyyyyyyyyyyyyyyy'

            request.headers.setdefault('User-Agent', ua)

 

            # the default user_agent_list composes chrome,IE,firefox,Mozilla,opera,netscape

 

    # for more user agent strings,you can find it in http://www.useragentstring.com/pages/useragentstring.php

    user_agent_list = [ \

        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1" \

        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", \

        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", \

        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", \

        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", \

        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", \

        "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", \

        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \

        "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \

        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", \

        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", \

        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", \

        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \

        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \

        "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", \

        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", \

        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", \

        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"

    ]


4. 对于断线重连

不多说。

def multi_session(session, *arg):

retryTimes = 20

while retryTimes>0:

try:

return session.post(*arg)

except:

print '.',

retryTimes -= 1

或者

def multi_open(opener, *arg):

retryTimes = 20

while retryTimes>0:

try:

return opener.open(*arg)

except:

print '.',

retryTimes -= 1

这样我们就可以使用multi_session或multi_open对爬虫抓取的session或opener进行保持。

或者设置失败后自动重试

    def get(self,req,retries=3):

        try:

            response = self.opener.open(req)

            data = response.read()

        except Exception , what:

            print what,req

            if retries>0:

                return self.get(req,retries-1)

            else:

                print 'GET Failed',req

                return ''

        return data


5. 多进程抓取


单线程太慢的话,就需要多线程了,这里给个简单的线程池模板 这个程序只是简单地打印了1-10,但是可以看出是并发地。

from threading import Thread

from Queue import Queue

from time import sleep

#q是任务队列

#NUM是并发线程总数

#JOBS是有多少任务

q = Queue()

NUM = 2

JOBS = 10

#具体的处理函数,负责处理单个任务

def do_somthing_using(arguments):

    print arguments

#这个是工作进程,负责不断从队列取数据并处理

def working():

    while True:

        arguments = q.get()

        do_somthing_using(arguments)

        sleep(1)

        q.task_done()

#fork NUM个线程等待队列

for i in range(NUM):

    t = Thread(target=working)

    t.setDaemon(True)

    t.start()

#JOBS排入队列

for i in range(JOBS):

    q.put(i)

#等待所有JOBS完成

q.join()


6. 对于Ajax请求的处理

对于“加载更多”情况,使用Ajax来传输很多数据。

它的工作原理是:从网页的url加载网页的源代码之后,会在浏览器里执行JavaScript程序。这些程序会加载更多的内容,“填充”到网页里。这就是为什么如果你直接去爬网页本身的url,你会找不到页面的实际内容。

这里,若使用Google Chrome分析”请求“对应的链接(方法:右键→审查元素→Network→清空,点击”加载更多“,出现对应的GET链接寻找Type为text/html的,点击,查看get参数或者复制Request URL),循环过程。

· 如果“请求”之前有页面,依据上一步的网址进行分析推导第1页。以此类推,抓取Ajax地址的数据。

· 对返回的json格式数据(str)进行正则匹配。json格式数据中,需从'\uxxxx'形式的unicode_escape编码转换成u'\uxxxx'的unicode编码。

7. 自动化测试工具Selenium

Selenium是一款自动化测试工具。它能实现操纵浏览器,包括字符填充、鼠标点击、获取元素、页面切换等一系列操作。总之,凡是浏览器能做的事,Selenium都能够做到。

如:如何在给定城市列表后,使用selenium来动态抓取去哪儿网的票价信息的代码?


8. 验证码识别

对于网站有验证码的情况,我们有三种办法:

· 使用代理,更新IP。

· 使用cookie登陆。

· 验证码识别。

使用代理和使用cookie登陆之前已经讲过,下面讲一下验证码识别。

可以利用开源的Tesseract-OCR系统进行验证码图片的下载及识别,将识别的字符传到爬虫系统进行模拟登陆。当然也可以将验证码图片上传到打码平台上进行识别。如果不成功,可以再次更新验证码识别,直到成功为止。

参考项目:验证码识别项目第一版:Captcha1

爬取有两个需要注意的问题:

· 如何监控一系列网站的更新情况,也就是说,如何进行增量式爬取?

· 对于海量数据,如何实现分布式爬取?


9.编码问题

在解析的过程中要注意编码问题,因为网页有UTF-8 编码的,也有GBK编码的,还有GB2312等等如果编码问题没有处理好,很有可能会导致输入输出异常,正则表达式匹配错误等问题.我的解决办法是坚持一个中心思想: "不管你是什么编码来的,到解析程序统一换成utf-8编码".比如有的网页是GBK编码,在处理之前我会先对它进行一个转码操作:

utf8_page = GBK_page.decode("GBK").encode("utf8")

同时在代码的初始化位置(或者是最开始部分)我一般会加上以下代码:

import sys

reload(sys)

sys.setdefaultencoding('utf8')

同时代码文件的编码方式也要保证是utf-8.

这样处理调理比较清晰,统一.不会出现一个utf-8的正则表达式和一个GBK的字符串做匹配最后啥也匹配不出来.或者输出的数据即有utf8编码的字符串,又有GBK编码的字符串导致IO错误.

如果事先不知道网页是什么编码,建议使用python 的第三方包chardet:https://pypi.python.org/pypi/chardet/ 它可以自动帮你识别出网页的编码.用法是:

import chardetimport urllib2

#可根据需要,选择不同的数据

TestData = urllib2.urlopen('http://www.baidu.com/').read()print chardet.detect(TestData)

分析

抓取之后就是对抓取的内容进行分析,你需要什么内容,就从中提炼出相关的内容来。

常见的分析工具有正则表达式BeautifulSouplxml等等。

存储

分析出我们需要的内容之后,接下来就是存储了。

我们可以选择存入文本文件,也可以选择存入MySQLMongoDB数据库等。

存储有两个需要注意的问题:

· 如何进行网页去重?

· 内容以什么形式存储?

Scrapy

Scrapy是一个基于Twisted的开源异构Python爬虫框架,在工业中应用非常广泛。