使用Python去爬虫

本文是笔者日常使用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("<your_url>") 
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="<your_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("<your_url>")
# 如果是POST请求
# req = urllib2.Request(...)
# res = opener.open(req)
html = res.read()
res.close()
获取特定元素的内容

通过BeautifulSoup来实现

import urllib
from bs4 import BeautifulSoup

res = urllib.urlopen("<your_url>") 
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("<your_url>") 
html = res.read()
res.close()

pat = re.compile(r'''<a href=(.*?)>''')  # 创建正则表达式
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("<your_url>") 
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("<your_url>") 
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("<your_url>") 
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个<li>元素的,结果只有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("<your_url>")  # 打开网页
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进行爬虫的一个简要记录,仅供大家参考。由于只是一个业余使用者,所以文中肯定有不少概念和代码使用上的错误,希望大家不吝指教。

原文发布于微信公众号 - 生信了(gh_ed36a29a9a9d)

原文发表时间:2019-07-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券