总览
对以前学过的爬虫进行总结,巩固知识,也希望能帮助他人。初学爬虫的时候,看了一段视频入门,再后来走了不少弯路,究其原因是没有对爬虫有个整体性的掌握。我想整里一份框架,概括自己所学的同时,给初学者一点指导和帮助。
学习爬虫,首先需要一门编程语言来实现,我推荐python,简单易学,上手快。还要学习一些相关的python爬虫包,如requests。再来,需要了解一下HTML网页的知识,因为你需要从网页中提取信息,不了解网页是不行。学会使用抓包工具,如fiddle4,其实大部分情况下,chrome浏览器的控制台就足够了。学会反爬,没办法,现在很多网站都有反爬,不会一点技巧,难以获得信息。如果你需要将信息存入数据库的话,你还要学会操作数据库,关系型数据库,如MySQL,非关系型数据库,如mongo,redis。当然,大规模抓取的时候,少不了用一些框架,如scrapy。简单的列举如下:
- python
- 爬虫相关包:requests, selenium, …
- HTML
- fiddler4,chrome控制台
- 反爬
- 数据库:MySQL, mongo, redis, …
- 爬虫框架:scrapy, PySpider, …
- …
python和HTML知识就不介绍了,相关信息很容易查阅,下面会依次介绍一下python里面和爬虫相关的包。
requests
requests是python3里面,爬虫的核心package,详情见官网。官网里面有详细的文档和教程。另一个相关包,selenium就不在本文介绍了。
编码问题
requests请求返回的结果有时候会显示为乱码,你可以指定编码。有时候,有些文本(例如表情)因为编码问题无法保存到txt,excel或者数据库,你可以使用编码后再解码的方式过滤掉。1
2
3
4import requests
r = requests.get('https://www.baidu.com')
r.enconding = 'utf8' # 指定编码
text = r.text.encode('utf8', 'ignore').decode('utf8') # 编码后解码
参数传递
传递参数一般使用字典构造,不同方法,传递参数的关键字也不同。1
2
3
4
5
6
7
8
9payload = {'key1': 'value1', 'key2': 'value2'}
# GET方法
r = requests.get("http://httpbin.org/get", params=payload)
# POST方法,需要注意的是,即使是POST方法,有时候仍然会有params参数
r = requests.post("http://httpbin.org/post", data=payload)
# POST有时会用json作为参数
r = requests.post(url, json=payload)
json返回
requests提供了json返回的便捷接口。1
2# 返回json解码后的dict
r.json()
需要注意的是,如果r.text
不是json格式,r.json()
解码失败。但即使解码成功,也不意味这你获得了你想要的结果,有些服务器在失败的响应中返回的也是json对象,你需要验证r.status_code
和内容。
定制HEADER
定制HEADER可以说是反爬的第一步,也是最简单的一步。很多网站会禁止机器访问,定制header是十分有必要的。header里面有很多参数,有些有用,有些就不是那么必要,看情况。1
2
3
4h = {
'user-agent:': '' # 可以在浏览器里面copy一下
}
r = requests.get(url, headers=headers)
Cookie和简单的反爬技巧
cookie是登录网站的记录,一般网站都会用cookie来限制爬虫。自动登录网站,获取cookie,大多数时候不是那么容易实现的。大多数网站都是通过javascript
脚本验证登录,中间经历多个传参和跳转,最终才能拿到cookie,微博登录就是典型的例子。还有的,加入了验证码。这就比较麻烦了,简单的验证码还好处理,复杂的验证码,几乎没办法。有些平台提供了验证码识别的api,当然是付费的。你可以调用他们的api,实现验证码输入。
当然,如果你对数据量需求不大,同时,网站服务器也没有针对账号和ip进行反爬,自己手动登录,获取cookie,进行爬虫是最简单的处理方案。针对账号反爬,会限制同一账户短时间内的过多访问,如果需要短时间大量爬取数据,多账号是必不可少的。ip限制也是类似,可以通过代理池解决。如果需要的数据量不大,控制访问速度就行了。1
2
3
4
5
6
7
8
9
10cookie = {
'key': 'value'
}
r = requests.get(url, cookies=cookie)
# 代理
proxies = {
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
}
r = requests.get("http://example.org", proxies=proxies)
关于cookie获取,还有个需要注意的地方。在实现自动登录获取cookie的时候,有些教程会建议使用session的会话方式记住cookie,这种方式有时可行,有的时候不行。究其根本,是在登录过程中进行了多次跳转,多次设置cookie,session的方式没能成功的设置最后的cookie。这种时候,需要自己去分析登录过程中的参数传递和cookie设置。有些小伙伴,可能在此过程中找不到cookie,发现r.cookies
是空的,或者没有自己要的cookies,可以在返回的请求头r.headers
里面找找看,它的set-cookie可能就是你需要的。r.request.headers
也可以试试,如果你某些参数设置错误,你的cookie可能会进到里面,在另外一篇博文里面看到的,具体是哪篇想不起来了。
信息提取
从HTML中提取信息方法很多,可以用re,xpath,BeautifulSoup,等等。re适用于任何文本的提取,就不说了。xpath和BeautifulSoup网上的教程也有很多,建议两种方法都掌握,都很简单。
xpath基础语法
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
1 | from lxml import etree |
BeautifulSoup的select
BeautifulSoup支持最常用的CSS selectors,通过select
方法调用。当然,BeautifulSoup也有自己的方法,使用起来也用方便。1
2
3
4from bs4 import BeautifulSoup
r = requests.get('https://www.baidu.com')
soup = BeautifulSoup(r.text, 'html.parser')
soup.select('title')
下表来自w3schools的CSS Selector Reference,只拿了一部分,完整表格参见网址,可能有部分不支持。
Selector | Example | Example description |
---|---|---|
.class | .intro | Selects all elements with class=”intro” |
.class1.class2 | <div class="name1 name2">...</div> |
Selects all elements with both name1 and name2 set within its class attribute |
.class1 .class2 | <div class="name1"><div class="name2">...</div></div> |
Selects all elements with name2 that is a descendant of an element with name1 |
#id | #firstname | Selects the element with id=”firstname” |
* | * | Selects all elements |
element | p | Selects all <p> elements |
element.class | p.intro | Selects all <p> elements with class=”intro” |
element,element | div, p | Selects all <div> elements and all <p> elements |
element element | div p | Selects all <p> elements inside <div> elements |
element>element | div > p | Selects all <p> elements where the parent is a <div> element |
element+element | div + p | Selects all <p> elements that are placed immediately after <div> elements |
element1~element2 | p ~ ul | Selects every <ul> element that are preceded by a <p> element |
[attribute] | [target] | Selects all elements with a target attribute |
[attribute=value] | [target=_blank] | Selects all elements with target=”_blank” |
[attribute~=value] | [title~=flower] | Selects all elements with a title attribute containing the word “flower” |
[attribute|=value] | [lang|=en] | Selects all elements with a lang attribute value starting with “en” |
[attribute^=value] | a[href^=”https”] | Selects every <a> element whose href attribute value begins with “https” |
[attribute$=value] | a[href$=”.pdf”] | Selects every <a> element whose href attribute value ends with “.pdf” |
[attribute*=value] | a[href*=”w3schools”] | Selects every <a> element whose href attribute value contains the substring “w3schools” |
数据库
我主要使用的数据库还是MySQL,mongo和redis使用很少,它们使用起来都不困难,python都提供了相应的包进行操作。pymysql
是python里面用来操作mysql数据库的包,详情参见官网。操作很简单,下面是官网的例子。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import pymysql.cursors
# Connect to the database
connection = pymysql.connect(host='localhost',
user='user',
password='passwd',
db='db',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
try:
with connection.cursor() as cursor:
# Create a new record
sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)"
cursor.execute(sql, ('webmaster@python.org', 'very-secret'))
# connection is not autocommit by default. So you must commit to save
# your changes.
connection.commit()
with connection.cursor() as cursor:
# Read a single record
sql = "SELECT `id`, `password` FROM `users` WHERE `email`=%s"
cursor.execute(sql, ('webmaster@python.org',))
result = cursor.fetchone()
print(result)
finally:
connection.close()
爬虫框架
实际上,小规模的爬取数据根本用不上框架。爬虫框架一般用来进行大规模爬取,搭建一个爬虫工程。我之前就用了scrapy搭建了一个抓取题库的项目,主要是网站上的选择题,存入数据库。scrapy是异步框架,但是没有实现多进程,需要自己分配爬取队列,来实现多进程的爬取。如果要进行分布式爬取,那建立一个爬取队列的数据库的很有必要的,保证每个框架间的队列是唯一的,不做重复爬取。爬虫框架使用起来很简单,教程很多,不展开了。
结尾
上面写的这些内容几乎都有尝试过,也有部分很少使用,或者没有经历。但就我这样十分业余的水平来说,应当是可堪用了。