原创: hxj7
本文是笔者日常使用Python进行爬虫的简要记录。
爬虫,简单说就是规模化地采集网页信息,因为网络像一张网,而爬虫做的事就像一只蜘蛛在网上爬,所以爬虫英文名就是spider。
爬虫可以做很多事情,比如抓取网页上的表格,下载歌曲、下载电影、模拟登录网站等等,基本上都是和网页相关的。当然,现在很多所谓的”手机爬虫“也出现了,原理类似。我们今天只说PC端的网页爬虫。
讲爬虫的技术文章数不胜数,很多编程语言也有现成的模块。笔者几乎只用Python,也只会用Python来进行爬虫,所以本文是讲如何用Python来进行爬虫。写这篇文章一是分享,二是把常用代码记录下来,方便自己查找。本文篇幅较长,主要分为以下五个部分:
• 理论基础
• 实现方法
• 注意点
• 难点
• 小结
爬虫,大多数时候是和网页打交道,所以和网页相关的常用技术多少要了解掌握。如:
• HTTP协议。主要是了解HTTP协议头。GET、POST方法等。常涉及到urllib、urllib2、requests模块。
• Cookie。一种服务器端记录客户端连接情况的工具。常涉及到cookielib模块。
• HTML。早期静态网页几乎都是HTML文本。
• Javascript。最流行的动态网页编程语言。可能会用到pyv8模块。
• CSS。讲如何布局、渲染网页的。
• AJAX。如何延迟显示网页内容。常涉及到json模块。
• DOM。抽象化的网页结构。常涉及到bs4(Beautiful Soup)、lxml模块。
• css-selector/xpath。如何定位网页元素。常涉及到bs4(Beautiful Soup)、lxml模块。
• 正则表达式。规则化地抽取文本。常涉及到re、bs4(Beautiful Soup)、lxml模块。
基本上这些都是要了解的。其实,谷歌浏览器Chrome提供的开发者工具就是一个强有力的辅助学习工具。可以借助它快速熟悉上述技术。
本着实用、简洁的原则。笔者将自己常用的代码整理如下:
只用到GET方法,没有什么复杂的情况。
# urllib模块可以很方便地实现 GET 方法。
import urllib
# eg: res = urllib.urlopen("http://youku.com")
res = urllib.urlopen("" )
html = res.read() # 像读取文件一样读取网页内容
info = res.info() # 返回的header信息
res.close() # 像关闭文件一样关闭网络连接
需要用到POST方法。
# urllib2模块可以方便地实现 POST 方法。
# 假设参数是 your_url?user=hxj5hxj5&passwd=testtest
import urllib, urllib2
headers = { # 请求头。根据情况调整,一般而言,最重要的是'User-Agent'一项。
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
paras = { # 请求参数。根据情况调整
'user': 'hxj5hxj5',
'passwd': 'testtest'
}
paraData = urllib.urlencode(paras)
req = urllib2.Request(url="" , data=paraData, headers=headers)
res = urllib2.urlopen(req)
html = res.read() # 像读取文件一样读取网页内容
res.close() # 像关闭文件一样关闭网络连接
需要用到cookie
import urllib2, cookielib
# cookielib模块可以很方便地操作cookie。
# 假设参数是 your_url?user=hxj5hxj5&passwd=testtest
cj = cookielib.CookieJar()
# 创建能自动处理cookie的实例,通过它来打开请求
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
# 如果是GET请求
res = opener.open("" )
# 如果是POST请求
# req = urllib2.Request(...)
# res = opener.open(req)
html = res.read()
res.close()
获取特定元素的内容
通过BeautifulSoup来实现
import urllib
from bs4 import BeautifulSoup
res = urllib.urlopen("" )
html = res.read()
res.close()
soup = BeautifulSoup(html, 'lxml')
taga = soup.select("a") # 根据CSS-selector来定位元素,返回列表
for a in taga:
print a["href"] # 打印节点的属性
print a.text # 打印节点的内容
通过 re 来实现
re模块是Python自带的创建、解析正则表达式的模块。
import urllib
import re
res = urllib.urlopen("" )
html = res.read()
res.close()
pat = re.compile(r'''''') # 创建正则表达式
result = pat.findall(html) # 返回所有符合条件的元素
for item in result:
print item # 打印元素内容
下载数据
# 使用urllib模块中的urlretrieve函数可以很方便地下载数据
# 假设要下载一张图片
import urllib
urllib.urlretrieve("http://just4test.cn/path/to/spider.jpg", "spider.jpg")
注意以上所说的几种功能都可以通过其他模块或方法实现,这里列出的只是笔者常用的而已
爬虫其实是一个很琐碎繁复的过程,有很多细节需要注意。下面列出一些笔者常遇到的问题。
数据被压缩过
有时候服务器端会将数据压缩后再传输到客户端,所以我们需要对压缩过的数据进行解压。常用的压缩方式就是gzip压缩。以此为例:
import urllib
import gzip
from StringIO import StringIO
def ungzip(data):
buf = StringIO(data)
f = gzip.GzipFile(fileobj=buf)
return f.read()
res = urllib.urlopen("" )
html = res.read()
content = res.info().get('Content-Encoding')
res.close()
if content == "gzip":
html = ungzip(html)
数据编码
Python中的字符串编码一直是很让人头疼的,爬虫中就经常会遇到这样的问题。
假设网页不是utf8编码(比如gbk编码)的,而你想要保持utf8编码,那么就需要进行编码的转换。
首先得判断网页编码格式,常用chardet模块实现。
#!/usr/bin/env python
#-*-coding:utf8-*-
import urllib
import chardet
res = urllib.urlopen("" )
html = res.read()
res.close()
encoding = chardet.detect(html)['encoding']
if encoding != 'utf8': # 以utf8为例
html = html.decode(encoding)
数据是json格式的
import urllib
import json
res = urllib.urlopen("" )
html = res.read()
isJson = 'json' in res.info().get('Content-Type')
res.close()
if isJson:
data = json.loads(html)
整站抓取
如果是一个要实现大规模抓取任务的爬虫,最好是使用成熟的爬虫框架如Scrapy。但是好在笔者目前还没有碰到过这种规模的任务,所以也没有用过Scrapy。下面只是从原理上大概探讨一下这种情形。
比较常见的比如抓取一个网站上的所有图片。如果把网站看成一棵树,而该网站的各个页面是树的各个节点,那么抓取所有图片就需要遍历所有节点(页面),并在每个节点(页面)上抓取该页面上的所有图片。那么可以用BFS(广度优先搜索)或者DFS(深度优先搜索)算法。
以BFS为例:
import urllib
from bs4 import BeautifulSoup
def spider(url, depth):
if depth > MAX_DEPTH: # 到达最大深度后就不再继续爬取了
return
res = urllib.urlopen(url)
html = res.read()
res.close()
soup = BeautifulSoup(html, 'lxml')
# 下载图片
pics = soup.select(a[href$=".jpg"]) # 假设只选取以jpg结尾的图片
for p in pics:
urllib.urlretrieve(p, str(picNum) + ".jpg")
picNum += 1
# 抓取新的页面链接
theUrls = soup.select(a[href$=".html"]) # href属性以html结尾的所有a标签
newUrls = set(theUrls) - oldUrls # 如果在oldUrl中出现过,就排除掉
oldUrls.update(newUrls) # 更新已有链接集合
for nu in newUrls:
spider(nu, depth + 1) # 对新的页面链接继续爬取
picNum = 0
MAX_DEPTH = 10
initUrl = "http://just4test.cn/" # 初始页面
oldUrls = set([initUrl])
spider(initUrl, 0) # 从深度0开始爬取,到达最大深度后停止
难点
爬虫的难点主要是如何绕过反爬虫机制。
限制频繁访问
为了减少服务器端的访问压力,一般都不会允许频繁访问网站(即不允许频繁发送请求)。为了解决这一点,所以最好能随机休息/暂停。
import urllib
import time
from random import randint
def randSleep(s=0, e=1):
t = randint(s * 1000, e * 1000) / 1000.0
time.sleep(t)
return t
for url in allUrls:
res = urllib.urlopen(url)
html = res.read()
res.close()
randSleep()
限制ip
有些服务器在判明是爬虫在爬取数据后,会封ip。这时候只能换一个ip。最好是能找到代理服务器,有一个ip池。封了一个ip,立即切换到另一个ip。
检查请求头
服务器端检查请求头,如果发现异常,就阻止请求。最常见的检查’User-Agent’一项,看是否是正常的真实的浏览器。或者检查’Referer’一项是否正常。这些都可以通过Chrome的开发者工具获取真实值后进行伪装。
当获取到相应值之后,可以一开始就在请求头中指定,也可以之后添加。
如果在一开始就指定,像这样:
import urllib, urllib2
headers = { # 请求头。
'Referer': 'http://just4test.cn/page1.html',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
paras = { # 请求参数。根据情况调整
'user': 'hxj5hxj5',
'passwd': 'testtest'
}
paraData = urllib.urlencode(paras)
req = urllib2.Request("http://just4test.cn/page2.html", data=paraData, headers=headers)
res = urllib2.urlopen(req)
html = res.read()
res.close()
或者之后添加
import urllib, urllib2
headers = { # 请求头。
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
paras = { # 请求参数。根据情况调整
'user': 'hxj5hxj5',
'passwd': 'testtest'
}
paraData = urllib.urlencode(paras)
req = urllib2.Request(url="http://just4test.cn/page2.html", data=paraData, headers=headers)
req.add_header('Referer', 'http://just4test.cn/page1.html') # 添加信息
res = urllib2.urlopen(req)
html = res.read()
res.close()
检查cookie
如上所说,可以使用cooklib模块自动处理cookie。但是当知道了具体的cookie值的时候,也可以直接将cookie添加到请求头中。
import urllib, urllib2
headers = { # 请求头。
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
paras = { # 请求参数。根据情况调整
'user': 'hxj5hxj5',
'passwd': 'testtest'
}
paraData = urllib.urlencode(paras)
req = urllib2.Request(url="http://just4test.cn/page2.html", data=paraData, headers=headers)
req.add_header('Cookie', 'USER_ID=hxj5hxj5') # 添加cookie
res = urllib2.urlopen(req)
html = res.read()
res.close()
复杂参数
有些网页请求的参数特别复杂,比如百度搜索’python’时的请求链接是"https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=python&oq=%25"(后面还有一长串),很多参数一眼看上去不知道是什么意思,也无从获取。这个时候写爬虫就很麻烦,因为你没法知道参数该用什么值。
遇到这种情况,一般有三种办法:
一是利用 Chrome 的开发者工具提供的设置断点等功能进行手动调试,一般请求链接中的参数还都是可以从 js 文件运行过程中得到的,所以手动调试有希望能获取参数值
二是利用诸如 v8 引擎(Python中有 pyv8 模块)执行 js 代码,从而获取参数值
三是利用 selenium 之类的工具绕过获取参数值这一步
人机验证
一旦碰到这种情况,以笔者目前的经验和水平,大多是不能靠基础模块和方法解决的。只能选择 selenium 这种工具来变通。
验证码
简单验证码可以直接用 OCR 工具破解,复杂一点的需要先去噪,然后建模训练进行破解。再复杂的就只能放弃或者人工输入验证码后让爬虫程序继续。
拖拽(点击)图形
如微博登录、12306购票都是这一类的。大多数也是靠 selenium 去想办法。
容错机制
爬虫要特别注意容错,不然很容易出现运行中途出错退出的情况。比如,网速不好,连接暂时丢失导致报错、字符串不规范(举一个例子,本来预期应该是有字符的地方是空的)从而导致出错、本来表格中预期有5个 < l i >元素的,结果只有4个从而报错等等。爬虫太繁琐了,很多细节都容易出错。所以一定要有容错机制。
比如我们可以最多尝试3次,3次都失败了就退出:
import urllib
import sys
maxTry = 3
isOK = 0
for _ in range(maxTry):
try:
res = urllib.urlopen(url)
html = res.read()
res.close()
except Exception as e: # 最好指定特定类型的exception
print str(e)
else:
isOK = 1
break
if not isOK:
print "urlopen failed."
sys.exit(1)
selenium
PhantomJS 以及 selenium 这一类的工具都可以用来进行浏览器自动化测试,就相当于你在操纵一个真实的浏览器。笔者只用过 selenium。它们显得笨重,但是功能的确强大,可以解决很多复杂的爬虫问题。网上有很多教程,其主要用法如下:
from selenium import webdriver
browser = webdriver.Chrome()
browser.implicitly_wait(10) # 设置默认等待时间
browser.get("" ) # 打开网页
print browser.page_source # 打印网页源代码
# 查找特定元素
tgtEle = browser.find_elements_by_css_selector('a') # 或者 browser.find_elements_by_xpath()
for e in tgtEle:
print e.text
print e.get_attribute('href')
tgtEle[0].click() # 点击元素
# 执行js代码
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
# cookie操作
get_cookies()
delete_all_cookes()
add_cookie({'name':'test', 'value':'123'})
# 选项卡操作(以切换选项卡为例)
browser.switch_to_window(browser.window_handles[1])
# 或者browser.switch_to.window(browser.window_handles[1])
browser.close() # 关闭浏览器
在Python中,爬虫相关的模块有不少,如果是日常简单的任务,用urllib,requests这些基础模块就够用了。但是如果是复杂的或者规模很大的爬虫,最好使用Scrapy之类的框架。最后要说的就是 selenium 是我们遇到困难时的好帮手。
本文是笔者使用Python进行爬虫的一个简要记录,仅供大家参考。由于只是一个业余使用者,所以文中肯定有不少概念和代码使用上的错误,希望大家不吝指教。
(公众号:生信了)