python爬虫学习笔记

总览

对以前学过的爬虫进行总结,巩固知识,也希望能帮助他人。初学爬虫的时候,看了一段视频入门,再后来走了不少弯路,究其原因是没有对爬虫有个整体性的掌握。我想整里一份框架,概括自己所学的同时,给初学者一点指导和帮助。

学习爬虫,首先需要一门编程语言来实现,我推荐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
4
import 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
9
payload = {'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
4
h = {
'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
10
cookie = {
'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
2
3
4
5
6
7
8
9
10
11
12
13
from lxml import etree
import request

r = requests.get('https://www.baidu.com')
html = etree.HTML(r.text)
result = etree.tostring(html)
# tostring()方法即可输出修正后的HTML代码,但是结果是bytes类型
print(result.decode('utf-8'))
result = html.xpath('//li')
# 返回的是list类型,每个元素是element类型,选取什么元素,就用基础语法进行组合就好,节点内文本使用text()
html.xpath('//li[@class="li"]/a/text()')
# 属性有多个值就需要用 contains() 函数
html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')

BeautifulSoup的select

BeautifulSoup支持最常用的CSS selectors,通过select方法调用。当然,BeautifulSoup也有自己的方法,使用起来也用方便。

1
2
3
4
from 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
28
import 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是异步框架,但是没有实现多进程,需要自己分配爬取队列,来实现多进程的爬取。如果要进行分布式爬取,那建立一个爬取队列的数据库的很有必要的,保证每个框架间的队列是唯一的,不做重复爬取。爬虫框架使用起来很简单,教程很多,不展开了。

结尾

上面写的这些内容几乎都有尝试过,也有部分很少使用,或者没有经历。但就我这样十分业余的水平来说,应当是可堪用了。

-------------本文结束感谢您的阅读-------------
0%