从-1开始的python爬虫

从-1开始的python爬虫

写在前面

  • 这篇长博客主要记录了我跟着一个公开课的内容从-1开始的爬虫经验,为什么是从-1呢,就是说我对python刚刚上手没几天,就开始学爬虫了QAQ
  • 假设我们都已经安装了pycharm并配置好了所有库
  • 我们要用的库是:pip3 install requests selenium beautifulsoup4 pyquery pymysql pymongo redis flask django jupyter
  • 除了pyquery这类的包需要网上下载,其他都用pip3 install 包名解决,但是有时候很慢,有时候失败 ,多试几次8
  • 谁能想到,仅仅安装这个包我就装了一个下午???

爬虫原理的讲解

概述

示例

什么是Request和Response?

示例

  • 浏览器就发送消息给该网址所在的服务器,这个过程叫做HTTP Request
  • 服务器收到浏览器发送的消息后,能够根据浏览器发送的内容做相应处理,然后把消息回传给浏览器。这个过程叫做HTTP Response
Request包含哪些方法?

示例

Response

示例

1
2
3
4
import requests
response = requests.get('http://www.baidu.com')
print(response.text)
print(response.status_code)#200 说明请求成功
  • 这三行代码就是我向百度发送了一个请求,然后把response返回给我们命名的response
  • 然后打印出网页源代码

  • 注意,这样的请求不能请求知乎这样的网站

能抓取怎样的数据

示例

解析方式

示例

  • request请求只会请求第一个html网页,而不会加载后面的Js文件,但是我们在dev-tool的Elements中看到的源代码是已经经过js渲染过后的,体量很大,行数很多,和一开始拿到的源代码完全不一样

那么,如何解决JavaScript渲染的问题

示例

  • 我们在python中引入了selenium库
1
2
3
4
5
import selenium
from selenium import webdriver
driver = webdriver.Chrome()
#driver.get('http://www.zhihu.com')
driver.get('http://www.taobao.com')

效果如下

示例

用库不能获得js渲染后的源代码,但是driver.page_source 可以

示例

如何存储文件

示例

Urllib 库的基本使用

使用方法

https://www.cnblogs.com/Caiyundo/p/12448948.html

urlopen
get 类型请求

返回源代码的所有内容,完成了爬虫的第一步,就是把网页给请求下来了

1
2
3
import urllib.request
response = urllib.request.urlopen('http://www.baidu.com')
print(response.read().decode('utf-8'))
post类型请求,加了data就是以post方式
1
2
3
4
5
import urllib.request
import urllib.parse
data = bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf-8')
response = urllib.request.urlopen('http://httpbin.org/post',data=data)
print(response.read())

示例

timeout 参数:超时就报错,否则就返回
1
2
3
4
import urllib.request
import urllib.parse
response = urllib.request.urlopen('http://httpbin.org/get',timeout=1)
print(response.read())

示例

调用urllib.error判断原因
1
2
3
4
5
6
7
8
9
import urllib.request
import socket
import urllib.error
try:
response = urllib.request.urlopen('http://httpbin.org/get',timeout = 0.1)
except urllib.error.URLError as e:
if isinstance(e.reason,socket.timeout):
print('Time Out!')
# 最后就返回Time Out
Response
响应类型
1
2
3
4
import urllib.request
response = urllib.request.urlopen('http://www.python.org')
print(type(response))
# 显示 <class 'http.client.HTTPResponse'>
状态码,响应头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import urllib.request
response = urllib.request.urlopen('http://www.python.org')
print(response.status) # 200
print(response.getheaders()) # 响应头
print(response.getheader('Server')) #nginx
""" 响应头如下,getheader是具体获得某个属性,比如这里('Server', 'nginx')
[('Connection', 'close'), ('Content-Length', '48894'), ('Server', 'nginx'),
('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'DENY'),
('Via', '1.1 vegur'), ('Via', '1.1 varnish'), ('Accept-Ranges', 'bytes'),
('Date', 'Fri, 01 May 2020 02:10:29 GMT'), ('Via', '1.1 varnish'),
('Age', '2038'), ('X-Served-By', 'cache-bwi5143-BWI, cache-hkg17922-HKG'),
('X-Cache', 'HIT, HIT'), ('X-Cache-Hits', '2, 1537'),
('X-Timer', 'S1588299030.614786,VS0,VE0'), ('Vary', 'Cookie'),
('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')]
"""
read方法,返回响应的内容
  • 就是网页的源代码(js渲染之后)
  • 示例
1
2
3
import urllib.request
response = urllib.request.urlopen('http://www.python.org')
print(response.read(),decode=('utf-8'))
Request
发送request对象 获得response的内容
1
2
3
4
5
import urllib.request
request = urllib.request.Request('http://www.python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
# 和上面的一样,返回网页源代码
加入headers的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from urllib import request,parse
url = 'http://httpbin.org/post'# 构造一个post请求
# 把headers加进来
headers = {
'User-Agent': 'Mozilla/5.0 (compatible;MSIE 5.5;Windows NT)',
'Host':'httpbin.org'
}
dict = {
'name' : 'Jason'
}
# 把data用bytes编码
data = bytes(parse.urlencode(dict),encoding='utf-8')
# 利用Request方法,传入url,data,headers,method这些参数
req = request.Request(url=url,data = data,headers=headers,method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

下面是返回的内容

示例

通过add_headers传入参数
1
2
3
4
5
6
7
8
9
10
11
from urllib import request,parse
url = 'http://httpbin.org/post'
dict = {
'name' : 'Jason'
}
data = bytes(parse.urlencode(dict),encoding='utf-8')
req = request.Request(url=url,data = data,method='POST')
# 通过add_headers 传入我们设定的headers
req.add_header('User-Agent','Mozilla/5.0 (compatible;MSIE 5.5;Windows NT)')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

内容和上图一样

Handler

handler就是辅助的工具,用来做更高级的操作

官方文档

代理
1
2
3
4
5
6
7
8
import urllib.request
proxy_handler = urllib.request.ProxyHandler({
# 这是我现在的代理(vpn)
'http':'http://127.0.0.1:1080'
})
opener = urllib.request.build_opener(proxy_handler)
response = opener.open('http://www.baidu.com')
print(response.read())
  • 传回来一堆东西,就是百度的源代码

示例

  • 如果换成httpbin.org,会返回一个ip地址

示例

稍做查询,我们可以知道这个199.193.124.87来自美国加利福尼亚洛杉矶

使用代理ip地址,可以伪装自己的ip地址,并保持切换,这样服务器就不会屏蔽我们这个爬虫

Coockie
  • cookie 是保存用户信息的文件,在爬虫中可以用来维持用户登陆状态。在dev-tools中清楚coockie,再次刷新浏览器,就需要重新登陆了

  • 利用cookie可以爬取一些需要认证的网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import http.cookiejar,urllib.request
# 首先把cookie声明为一个cookieJar的对象
cookie = http.cookiejar.CookieJar()
# 借助handler 处理cookie
handler = urllib.request.HTTPCookieProcessor(cookie)
# build_opener把handler传过来
opener = urllib.request.build_opener(handler)
# 最后用opener打开网站
response = opener.open('http://www.baidu.com')
# 遍历打印出cookie中的内容
for item in cookie:
print(item.name + '='+item.value)

'''
BAIDUID=2D4FA5F4570DED9E85DFD1316A950B5A:FG=1
BIDUPSID=2D4FA5F4570DED9E52F2D6C2838FE7A0
H_PS_PSSID=1442_31325_21078_31423_31341_31463_30824_31163
PSTM=1588302459
BDSVRTM=0
BD_HOME=1
'''
  • 如果cookie没有失效,那么可以一直使用这个cookie维持登录状态
利用MozillaCookieJar保存我们的cookie文件
1
2
3
4
5
6
7
import http.cookiejar,urllib.request
filename = "cookie.txt"
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True,ignore_expires=True)

运行之后,文件夹里多出了一个cookie.txt 文件

示例

也可以用LWPcookieJar保存我们的cookie,只是格式不同

示例

用load方法来把文件中的cookie赋值给新的cookie
1
2
3
4
5
6
7
8
9
10
11
import http.cookiejar,urllib.request
cookie = http.cookiejar.LWPCookieJar()
# 这里我们已经把cookie文件保存下来,下面只是读取文本文件,赋值
cookie.load('cookie.txt',ignore_expires=True,ignore_discard=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
# cookie没有过期的话,可以再次爬取我们的网页
# 如果网页是要登录才能看到的,那么这样就能保持我们的登陆状态
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))
# 最后打印的内容就是我们的网页源码
异常处理

官方文档

URLError
1
2
3
4
5
6
7
from urllib import request,error
try:
response = request.urlopen('http://cuiqingcai.com/index.html')
except error.URLError as e:
print(e.reason)
# 打印 Not Found
# 改成 http://jasonxqh.github.io 不显示错误,因为请求成功
HTTPError

示例

1
2
3
4
5
6
7
8
9
10
11
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('http://www.baidu.com',timeout=0.01)
except urllib.error.URLError as e:
print(type(e.reason))
if isinstance(e.reason,socket.timeout):
print('Time Out')
#打印<class 'socket.timeout'>
# Time Out
URL解析
urlparse
  • 把URL拆分成几个标准的部分
1
2
3
4
5
6
from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result),result)
# 打印内容如下,
<class 'urllib.parse.ParseResult'> ParseResult(scheme='http', netloc='www.baidu.com',
path='/index.html', params='user', query='id=5', fragment='comment')
  • 加上协议参数,如果本来就有http开头,那么第二个参数是不会生效的
1
2
3
4
5
from urllib.parse import urlparse
result = urlparse('www.baidu.com/index.html;user?id=5#comment',scheme='https')
print(result)
# 打印内容如下
ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
  • allow_fragments参数
1
2
3
4
5
6
from urllib.parse import urlparse
result=urlparse('http://www.baidu.com/index.html;user id=5#comment',allow_fragments=False)
print(result)
# 打印内容如下 fragment 置空,拼接到上面一个非空参数中,如果query也为空,那么再拼到params中
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user',
query='id=5#comment', fragment='')
urlunprse
  • 拼接url,就是urlparse的反函数

示例

urljoin

示例

拼接两个字符串,因为一个url可以分成很多部分,如果后面的字符串的部分未出现,前面的来补,后面的出现了,那么以后面的为准

urlencode

把字典对象转换成get请求参数

1
2
3
4
5
6
7
8
9
10
from urllib.parse import  urlencode
params = {
'name':'Jason',
'age' :18
}
base_url = 'http://www.baidu.com?'
url = base_url+urlencode(params)
print(url)
# 打印内容
http://www.baidu.com?name=Jason&age=18

Requests库详解

  • requests库的代码量相较于urllib更为简单,而且是基于urllib3编写的库。所以在写爬虫代码的时候建议用Requests库

实例引入

1
2
3
4
5
6
7
8
import requests
response = requests.get('http://www.baidu.com')
print(type(response))
print(response.status_code)
print(type(response.text))
print(response.text)#和urllib中的read()方法的功能相同
print(response.cookies)#不需要像urllib一样先声明一个cookie对象和handler
# 可以把我们想要的信息迅速打印出来

示例

各种请求方式

1
2
3
4
5
6
import requests
requests.post('http://httpbin.org/post')
requests.put('http://httpbin.org/put')
requests.delete('http://httpbin.org/delete')
requests.options('http://httpbin.org/get')
requests.head('http://httpbin.org/get')

基本get请求

基本写法

1
2
3
import requests
response = requests.get('http://httpbin.org/get')
print(response.text)

带参数的Get请求

原来这么写
1
2
3
import requests
response = requests.get('http://httpbin.org/get?name=Jason&age=19')
print(response.text)
现在这么写
1
2
3
4
5
6
7
import requests
data = {
'name':'Jason',
'age':22
}
response = requests.get('http://httpbin.org/get',params=data)
print(response.text)
  • 返回内容都是一样的。但是后面的更加直观易懂,不需要自己编码

解析json

1
2
3
4
5
import requests
response = requests.get('http://httpbin.org/get')
print(type(response.text))
print(response.json())
print(type(response.json()))

示例

  • response.json()相当于json库中的json.loads(response.txt)的用法

获取二进制数据

1
2
3
4
5
import requests
response = requests.get('http://github.com/favicon.ico')
print(type(response.text),type(response.content))
print(response.text)
print(response.content)
保存数据
1
2
3
4
5
import requests
response = requests.get('http://github.com/favicon.ico')
with open('favicon.ico','wb')as f:# wb是写入模式
f.write(response.content)
f.close()
  • 我们看到图片就这样被下载了

示例

添加headers

1
2
3
import requests
response = requests.get('https://www.zhihu.com/explore')
print(response.text)

不加headers,会被某些网站ban掉

示例

  • 添加了headers后,好起来了
1
2
3
4
5
6
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36',
}
response = requests.get('https://www.zhihu.com/explore',headers=headers)
print(response.text)

基本post请求

可以非常方便的传入一个字典,在urllib还要转码,配置,比较繁琐

1
2
3
4
import requests
data = {'name':'jason','age':22}
response = requests.post("http://httpbin.org/post",data=data)
print(response.text)

示例

  • 加入一个headers
1
2
3
4
5
6
7
8
9
10
11
import requests
data = {'name':'jason','age':22}
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65
Safari/537.36',
}
response =
requests.post("http://httpbin.org/post",data=data,headers=headers)
print(response.json())
# 打印json文件格式

响应

response属性

1
2
3
4
5
6
7
import requests
response = requests.get('http://www.jianshu.com')
print(type(response.status_code),response.status_code)
print(type(response.headers),response.headers)
print(type(response.cookies),response.cookies)
print(type(response.url),response.url)
print(type(response.history),response.history)

状态码判断

1
2
3
4
import requests
response = requests.get('http://www.jianshu.com')
exit()if not response.status_code==requests.codes.not_found else print('Request Not Found')
# not_found 可以更改为任何一个状态码后的文字内容

示例

示例

高级操作

文件上传

1
2
3
4
5
6
import requests
# 把这个文件读取出来
files = {'file':open('favicon.ico','rb')}
# 利用post操作实现文件的上传
response = requests.post('http://httpbin.org/post',files=files)
print(response.text)

示例

获取cookie

不用像urllib一样,用一个cookiejar然后再用handler获取cookie了

1
2
3
4
5
6
7
8
import requests
response = requests.get('http://www.baidu.com')
print(response.cookies)
for key,value in response.cookies.items():
print(key+'='+value)
''' <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
BDORZ=27315
'''

会话维持

模拟登陆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
# Session()方法是实现在同一个浏览器实现get和set
s = requests.Session()
# 用sesson对象发起两次请求
s.get('http://httpbin.org/cookies/set/number/123456789')
response = s.get('http://httpbin.org/cookies')
print(response.text)
```
输出内容如下,说明第二次访问的时候cookies已经保存下来了,说明会话维持住了
{
"cookies": {
"number": "123456789"
}
}
```

证书验证

  • 如果要访问的网站的证书是不合法的,就会抛出一个错误,要避免这个错误就要设定一个verify参数
1
2
3
import requests
response = requests.get('http://www.12306.cn',verify=False)#会warning
print(response.status_code)
1
2
3
4
5
6
import requests
# 通过这样引入一个原生包可以避免warning的发生
from requests.packages import urllib3
urllib3.disable_warnings()
response = requests.get('http://www.12306.cn',verify=False)
print(response.status_code)

代理设置

示例

第一种是普通的,第二种是需要密码的,第三种是用socks代理的,需要pip一下socks包

超时设置

1
2
3
4
import requests
response = requests.get('http://www.taobao.com',timeout=0.01)
print(response.status_code)
# 会报错

认证设置

示例

异常处理

示例

示例

官方文档

BeautifulSoup 的基本用法

  • 灵活又方便的网页解析库,处理高效,支持多种解析器。利用它不用编写正则表达式即可方便地实现网页信息的爬取

解析库

示例

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
html = """
<!doctype html>
<html lang="zh" data-hairline="true" data-theme="light"><head><meta charSet="utf-8"/><title data-react-helmet="true">请问心脏线r=a(1+ cosθ)的图像怎么画,和a有什么关系? - 知乎</title><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/><meta name="renderer" content="webkit"/>
height: env(safe-area-inset-top) !important;

}
.u-safeAreaInset-bottom {
height: constant(safe-area-inset-bottom) !important;
height: env(safe-area-inset-bottom) !important;

}
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.prettify())# 就可以梅花我们残缺的html文件
print(soup.title.string)# 获取到我们html中的title
  • 我们看到这就是结果

示例

  • 如果没有title标签,那么就会报错

标签选择器

  • 这种选择器的选择速度是非常快的,根据标签的名字来选择
  • 但是不能满足我们的一些需求,单纯用标签来选择是远远不够的

选择元素/标签

1
2
3
4
5
6
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.title)
print(type(soup.title))
print(soup.head)
print(soup.p)
  • 我们很简单的把知乎的源代码当作html文件,这样我们看到打印出来的内容:

示例

获取名称:返回最外层标签的名称

1
2
3
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.title.name)

获取属性

示例

1
2
3
4
5
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.p.attr('name'))
print(soup.p('name'))
# 输出dromouse

获取内容

1
2
3
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.title.string)# 获取到我们html中的title中的内容

嵌套选择

1
2
3
4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
# 获取head中的title标签的string属性
print(soup.head.title.string)

子节点和子孙节点

  • 对子节点输出 : child
1
2
3
4
5
6
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.p.child)
# child 会输出所有的子节点,返回的是迭代器
for l,child in enumerate(soup.p.child):
print(l,child)
  • 对所有子孙节点输出: descendants
1
2
3
4
5
6
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.p.descendants)
# descendants 会把子节点子孙节点全部打印出来,返回迭代器
for l,child in enumerate(soup.p.descendants):
print(l,child)

父节点和祖先节点

  • 对指定标签的父节点进行输出:parent
1
2
3
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.a.parent)# 对a标签的父节点进行输出
  • 对指定标签的父节点和祖父节点进行输出:parents
1
2
3
4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.a.parents)
print(list(enumerate(soup.a.parents)))

兄弟节点

  • next_siblings 和 previous_siblings
1
2
3
4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(list(enumerate(soup.a.next_siblings)))# 后面的兄弟节点
print(list(enumerate(soup.a.previous_siblings)))# 前面的兄弟节点

标准选择器 find 和 find_all

find_all(name,attrs,recursive,text,**kwargs)

name
  • 这里是找标签为ul的代码,并且返回一个迭代器
1
2
3
4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all('ul'))
print(type(soup.find_all('ul')[0]))
  • 先把每一个ul拿出来,再嵌套一层遍历
1
2
3
4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
for ul in soup.find_all('ul'):
print(ul.find_all('li'))
attrs
  • 传入一个字典,键名是属性名,键值是属性值
1
2
3
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all(attrs=('id':'list-1')))
  • 直接用等于会更加方便,也不会用到attrs了
  • class比较特殊,需要用class_
1
2
3
4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))
text
  • 对文本的内容进行选择
1
2
3
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all(text='Foo'))# 直接返回里面的name,可以做内容匹配

find(name,attrs,recursive,text,**kwargs)

  • find返回单个元素,find_all 返回所有元素

  • find_parents() 和find_parents()

    • 前者返回所有祖先节点,后者返回父节点
  • find_previous_siblings()和find_previous_sibling ()
    • 前者返回所有的前面的兄弟节点,后者只返回前面的第一个兄弟节点
  • find_next_siblings()和find_next_sibling ()
    • 前者返回所有的后面的兄弟节点,后者只返回后面的第一个兄弟节点
  • find_all_next{} 和 find_next()
    • 前者返回节点后所有符合条件的节点,后面返回第一个符合条件的节点
  • find_all_previous{}和find_previous()
    • 同理

css 选择器 select()

  • 通过select()直接传入CSS选择器即可完成选择
1
2
3
4
5
6
7
8
9
10
11
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
# 如果标签中又class,那么选择器前面要加 . 查找panel里面的panel-heading
# 如<div class = "panel"><div class = "panel-heading">
print(soup.select('.panel .panel-heading'))
# 选择ul中的li
print(soup.select('ul li'))
# 选择id中的内容,那么要加#
#如 <ul class = "xxx" id ="list-2">
print(soup.select('#list-2 .element'))
print(type(soup.select('ul')[0]))
  • 层层迭代
1
2
3
4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
for ul in soup.select('ul'):
print(ul.select('li'))

获取属性

1
2
3
4
5
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
for ul in soup.select('ul'):
print(ul('id'))
print(ul.attr['id'])

获取内容

  • 把所有指定的标签中的内容进行输出
1
2
3
4
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
for li in soup.select('li'):
print(li.get_text())

总结

  • 推荐使用lxml解析库,必要时使用html.parser
  • 标签选择筛选功能弱但是速度快
  • 建议使用find()或者find_all()来匹配结果
  • 如果对CSS选择器熟悉用 select()
  • 记住常用的获取属性和文本值的方法

pyquery 的基本用法

  • 强大灵活的网页解析库。熟悉jQuery的语法乐意选择PyQuery

Selenium的基本用法

  • 自动化测试工具,支持多种浏览器,爬虫中主要用来解决JavaScript渲染的问题

  • 当urllib,requests无法获得渲染之后的源代码的时候,selenium可以派上用场

基本用法

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
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
# 声明浏览器的一个对象
browser= webdriver.Chrome()
try:
# 传入网址
browser.get('http://www.baidu.com')
# 把id=kw的内容赋值给input,这里就是搜索框
input = browser.find_element_by_id('kw')
# 在搜索框中敲入Python
input.send_keys('Python')
# 点击回车
input.send_keys(Keys.ENTER)
# 等待浏览器,知道content_left被加载出来
wait = WebDriverWait(browser,10) wait.until(EC.presence_of_all_elements_located((By.ID,'content_left')))
# 打印url
print(browser.current_url)
# 打印cookies
print(browser.get_cookies())
# 打印网页源代码
print(browser.page_source)
finally:
# 关闭网页
browser.close()

声明浏览器对象

1
2
3
4
5
6
7
from selenium import webdriver
# 只有安装了该浏览器,才能运行,建议使用chrome,因为会直接弹出,比较直观
browser= webdriver.Chrome()
browser= webdriver.Firefox()
browser= webdriver.Edge()
browser= webdriver.Safari()
browser= webdriver.PhantomJS()

访问页面

1
2
3
4
5
from selenium import webdriver
browser= webdriver.Chrome()
browser.get('http://www.taobao.com')
print(browser.page_source)
browser.close()

查找元素

单个元素。find_element

  • 找到输入框,输入信息。找到一些按钮,进行一些操作等等
1
2
3
4
5
6
7
8
9
10
11
12
13
from selenium import webdriver
browser= webdriver.Chrome()
browser.get('http://www.taobao.com')
# 通过id选择
input_first = browser.find_element_by_id('q')
# 通过css_selector选择
input_second = browser.find_element_by_css_selector('#q')
# 通过xpath选择
input_third = browser.find_element_by_xpath('//*[@id="q"]')
print(input_first)
print(input_second)
print(input_third)
browser.close()

可以看到这三种查询结果是一样的

示例

  • 常见的查找方式
1
2
3
4
5
6
7
browser.find_element_by_xpath()
browser.find_element_by_name()
browser.find_element_by_link_text()
browser.find_element_by_partial_link_text()
browser.find_element_by_tag_name()
browser.find_element_by_class_name()
browser.find_element_by_css_selector()
  • 通用查找方式
1
2
3
4
5
6
7
8
from selenium import webdriver
from selenium.webdriver.common.by import By
browser= webdriver.Chrome()
browser.get('http://www.taobao.com')
# 直接调用find_element,第一个参数是By.查找类型,第二个就是查找内容
input_first = browser.find_element(By.ID,'q')
print(input_first)
browser.close()

多个元素 find_elements

1
2
3
4
5
6
7
from selenium import webdriver
browser= webdriver.Chrome()
browser.get('http://www.taobao.com')
lis = browser.find_elements_by_css_selector('.service-bd li')
print(lis)
# 最后输出一个元组
browser.close()
  • 搜索的就是淘宝网的一些分类

示例

  • 通用查找方式
1
2
3
4
5
6
7
from selenium import webdriver
from selenium.webdriver.common.by import By
browser= webdriver.Chrome()
browser.get('http://www.taobao.com')
lis = browser.find_elements(By.CSS_SELECTOR,'.service-bd li')
print(lis)
browser.close()

元素交互操作

  • 对获取的元素调用交互方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('http://www.taobao.com')
# 找到浏览框
input = browser.find_element_by_id('q')
# 输入iPhone
input.send_keys('iPhone')
# 保持一秒
time.sleep(1)
# 清空
input.clear()
# 输入iPad
input.send_keys('iPad')
# 找到搜索按钮
button = browser.find_element_by_class_name('btn-search')
# 点击搜索按钮
button.click()

交互动作

  • 把动作附加到动作链中串行执行

示例

执行JavaScript

  • 有些动作没有分装成api,所以我们传入JavaScript语句实现这个动作
1
2
3
4
5
6
7
8
from selenium import webdriver
from selenium.webdriver import ActionChains
browser = webdriver.Chrome()
browser.get('http://www.zhihu.com/explore')
# 把页面下拉到底部
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
# 显示内容
browser.execute_script('alert("To Bottom")')

获取元素信息

获取属性

1
2
3
4
5
6
7
8
from selenium import webdriver
from selenium.webdriver import ActionChains
browser = webdriver.Chrome()
url = 'http://www.zhihu.com/explore'
browser.get(url)
logo = browser.find_element_by_id('zh-top-link-logo')
print(logo)
print(logo.get_attribute('class'))

获取文本值

1
2
3
4
5
6
from selenium import webdriver
browser = webdriver.Chrome()
url = 'http://www.zhihu.com/explore'
browser.get(url)
input = browser.find_element_by_class_name('zu-top-add-question')
print(input.text)

获取ID,位置,标签名,大小

1
2
3
4
5
6
7
8
9
from selenium import webdriver
browser = webdriver.Chrome()
url = 'http://www.zhihu.com/explore'
browser.get(url)
input = browser.find_element_by_class_name('zu-top-add-question')
print(input.id)
print(input.location)
print(input.tag_name)
print(input.size)# 宽高

Frame

  • 在父级frame中查找子级内容,必须实现frame切换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from selenium import webdriver
import time
from selenium.common.exceptions import NoSuchElementException
browser = webdriver.Chrome()
url= 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
#请求
browser.get(url)
# 传入frame id 实现切换
browser.switch_to.frame('iframeResult')
# 找到拖拽的对象并打印
source = browser.find_element_by_css_selector('#draggable')
print(source)
try:
# 尝试在子级frame中查找父frame
logo = browser.find_element_by_class_name('logo')
except NoSuchElementException:
print('No Logo')
# 切换到父frame
browser.switch_to.parent_frame()
# 再次查找logo
logo= browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)

示例

等待

隐式等待

  • 当使用了隐式等待执行测试的时候,如果WebDriver没有在Dom中找到元素,将继续等待,超出设定时间后抛出找不到元素的异常,换句话说,当查找元素或元素并没有立即出现的时候,隐式等待将等待一段时间再查找DOM,默认时间为0
1
2
3
4
5
6
from selenium import webdriver
browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('http://www.zhihu.com/explore')
input = browser.find_element_by_class_name('zu-top-add-question')
print(input)

显示等待

  • 指定一个等待条件
  • 指定一个最长等待时间
  • 如果在等待时间内符合等待条件,那么继续等待,等待到超出等待时间为止

示例

下面是我们的等待条件,非常灵活可变

前进后退

1
2
3
4
5
6
7
8
9
10
11
12
13
from selenium import webdriver
import time
browser = webdriver.Chrome()
# 依次打开以下网站
browser.get('http://www.baidu.com/')
browser.get('http://www.taobao.com/')
browser.get('http://www.python.org/')
# 退回上一个网站
browser.back()
time.sleep(1)
# 前进到下一个网站
browser.forward()
browser.close()

Cookies

1
2
3
4
5
6
7
8
9
from selenium import webdriver
browser = webdriver.Chrome()

browser.get('http://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name':'name','domain':'www.zhihu.com','value':'jason'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())
  • 我们看到第二次打印的时候我们加上去的cookie已经出现了

示例

选项卡(窗口)管理

  • 通过执行一个js代码,打开一个新窗口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import time
from selenium import webdriver
browser = webdriver.Chrome()

browser.get('http://www.baidu.com')
# 新建一个窗口
browser.execute_script('window.open()')
#打印选项卡代号
print(browser.window_handles)
# 切换窗口
browser.switch_to.window(browser.window_handles[1])
browser.get('http://www.taobao.com')
time.sleep(1)
# 回切
browser.switch_to.window(browser.window_handles[0])
browser.get('http://python.org')

异常处理

示例

用Requests+正则表达式爬取猫眼电影Top100

  • 流程框架

示例

源码一览

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import re
from multiprocessing import Pool
import requests
import json
from requests.exceptions import RequestException
# 猫眼电影直接爬不了,我伪装了一个headers和一个代理
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"}
proxies = {
"http": "http://127.0.0.1:1080"
}
# 写这个get函数还需要进行一个错误判断,否则容易中断
def get_one_page(url):
try:
response = requests.get(url,proxies = proxies,headers=headers)
response.encoding = 'utf-8'
if response.status_code ==200:
return response.text
return None
except RequestException:
return None
# 利用正则表达式,筛选出满足我们正则表达式的内容,

def parse_one_page(html):
pattern = re.compile('<dd>.*?board-index.*?>(\d*)</i>.*?data-src="(.*?)".*?name"><a'+'.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime"(.*?)</p>'+'.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S)
items = re.findall(pattern,html)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2],
'actor': item[3].strip()[3:],
'time': item[4].strip()[5:],
'score': item[5]+item[6]
}

# 把我们的item写入 一个txt文件
def write_to_file(content):
with open('result.txt','a',encoding='utf-8') as f:
f.write(json.dumps(content,ensure_ascii=False)+'\n')
f.close()

# 最后用一个字符串叠加的方式,抓取1-10页的所有内容
def main(offset):
url = 'https://maoyan.com/board/4?offset='+str(offset)
html = get_one_page(url)
for item in parse_one_page(html):
print(item)
write_to_file(item)
# 用一个遍历的方式,爬取1-10张网页,相当于运行10次main函数,每次爬取一张
if __name__ == '__main__':
for i in range(10):
main(i*10)

成果展示

示例

分步解析

第一步:获取网页源代码

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 requests
from requests.exceptions import RequestException
# 猫眼电影直接爬不了,我伪装了一个headers和一个代理
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"}
proxies = {
"http": "http://127.0.0.1:1080"
}
def get_one_page(url):
try:
# 在get()中加入proxies和headers参数
response = requests.get(url, proxies=proxies, headers=headers)
# 注意,这里必须要设定encoding参数,否则会乱码
response.encoding = 'utf-8'
# 做一个状态码的判断
if response.status_code == 200:
return response.text
return None
except RequestException:
return None
# 最后用一个字符串叠加的方式,抓取1-10页的所有内容
def main(offset):
url = 'https://maoyan.com/board/4'
html = get_one_page(url)
print(html)

if __name__ == '__main__':
main()

第二步:对目标信息编写正则表达式

  • 我们可以看到,我们得信息源如下

示例

括号括起来的内容,就是我们想要的结果

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
29
30
<dd> 			获取到标签 <dd>
.*? 匹配任意字符串
board-index 匹配 board-index
.*? 匹配任意字符串
> 匹配>
(\d*) 匹配我们要的排名,即括号内的内容
</i> 匹配标签</i>
.*? 匹配任意字符串
data-src="(.*?)"匹配封面图片得url,即括号内的内容
.*?name"> 匹配name标签
<a.*?> 匹配a标签和后续内容
(.*?) 匹配我们电影名字,即括号内的内容
</a> 匹配a标签
.*? 匹配任意字符串
star"> 匹配star标签
(.*?) 匹配我们主演人名,即括号内的内容
</p> 匹配p标签
.*? 匹配任意字符串
releasetime“> 匹配releasetime“>
(.*?) 匹配上映时间,即括号内的内容
.*?integer"> 匹配任意字符串和integer">
(.*?) 匹配评分中的整数内容,即括号内的内容
</i> 匹配</i>标签
.*?fraction"> 匹配任意字符串和fraction">
(.*?) 匹配评分中的小数内容,即括号内的内容
.*? 匹配任意字符串
,
re.S 匹配任意字符,包括换行符
合起来就是
pattern = re.compile('<dd>.*?board-index.*?>(\d*)</i>.*?data-src="(.*?)".*?name"><a'+'.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime"(.*?)</p>'+'.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S)
1
2
3
4
def parse_one_page(html):
pattern = re.compile('<dd>.*?board-index.*?>(\d*)</i>.*?data-src="(.*?)".*?name"><a.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime"(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S)
items = re.findall(pattern,html)
print(items)
  • 把他们变成字典形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def parse_one_page(html):
pattern = re.compile('<dd>.*?board-index.*?>(\d*)</i>.*?data-src="(.*?)".*?name"><a.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime"(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S)
items = re.findall(pattern,html)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2],
'actor': item[3].strip()[3:],
# 主演:克里斯蒂安·贝尔,希斯·莱杰,阿伦·伊克哈特
# 这里直接从第三个字符冒号后开始切片截取
'time': item[4].strip()[5:],
#上映时间:2008-07-14(阿根廷)
# 这里直接从冒号后面开始切片
'score': item[5]+item[6]# 把评分拼接
}
这一步的代码
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import re #即regular expression
from multiprocessing import Pool
import requests
from requests.exceptions import RequestException

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"}
proxies = {
"http": "http://127.0.0.1:1080"
}

def get_one_page(url):
try:
response = requests.get(url, proxies=proxies, headers=headers)
response.encoding = 'utf-8'
if response.status_code == 200:
return response.text
return None
except RequestException:
return None


def parse_one_page(html):
pattern = re.compile(
'<dd>.*?board-index.*?>(\d*)</i>.*?data-src="(.*?)".*?name"><a' + '.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime"(.*?)</p>' + '.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',
re.S)
items = re.findall(pattern, html)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2],
'actor': item[3].strip()[3:],
'time': item[4].strip()[5:],
'score': item[5] + item[6]
}


def main( ):
url = 'https://maoyan.com/board/4?'
html = get_one_page(url)
for item in parse_one_page(html):
print(item)



if __name__ == '__main__':
main()
效果如图

示例

第三步,把他们写入到我的文件中

1
2
3
4
5
6
7
8
# 把我们的item写入 一个txt文件       
def write_to_file(content):
# 用一个with的形式,'a'代表往后追加的方式,encoding代表编码,否则就是一堆乱七八糟的
with open('result.txt','a',encoding='utf-8') as f:
# 使用json的api需要导入json包
# 防止乱码,还需要这样,把ensure_ascii设置成False
f.write(json.dumps(content,ensure_ascii=False)+'\n')
f.close()
加入这个函数后,效果如图

示例

第四步,通过字符串改造的方式爬取10页

1
2
3
4
5
6
7
8
9
10
11
# 最后用一个字符串叠加的方式,抓取1-10页的所有内容    
def main(offset):
url = 'https://maoyan.com/board/4?offset='+str(offset)
html = get_one_page(url)
for item in parse_one_page(html):
print(item)
write_to_file(item)
# 用一个遍历的方式,爬取1-10张网页,相当于运行10次main函数,每次爬取一张
if __name__ == '__main__':
for i in range(10):
main(i*10)

番外,利用多线程实现秒爬(但是这样Top100顺序就乱了)

  • 开头引入
1
from multiprocessing import Pool
  • 对结尾进行改造
1
2
3
4
5
if __name__ == '__main__':
# 这是一个进程池,池还没有满就创建进程并运行,否则就待命
pool = Pool()
# 把map中的元素提取出来创建进程
pool.map(main, [i*10 for i in range(10)])

如法炮制,我也制作了一个爬取豆瓣top250的电影

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import re
import json
import requests
from requests.exceptions import RequestException

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"}
proxies = {
"http": "http://127.0.0.1:1080"
}

def get_one_page(url):
try:
response = requests.get(url, proxies=proxies, headers=headers)
# 注意,这里必须要设定encoding参数,否则会乱码
response.encoding = 'utf-8'
# 做一个状态码的判断
if response.status_code == 200:
return response.text
return None
except RequestException:
return None
def parse_one_page(html):
pattern = re.compile('<li>.*?em.*?>(\d*)</em>.*?src="(.*?)".*?<span class="title">(.*?)</span>.*?"title">&nbsp;/&nbsp;(.*?)</span>.*?other">&nbsp;/&nbsp;(.*?)</span>.*?bd">.*?class="">\s(.*?)&nbsp;&nbsp;&nbsp;(.*?)<br>(.*?)&nbsp;/&nbsp;(.*?)&nbsp;/&nbsp;(.*?)</p>.*?average">(\d.\d).*?inq">(.*?)</span>',re.S)
items = re.findall(pattern, html)
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2],
'other title': item[3]+item[4],
'director': item[5].strip()[3:],
'actor': item[6].strip()[3:] ,
'time':item[7].strip()[0:],
'country':item[8],
'sort':item[9].strip()[:20],
'score':item[10],
'quote':item[11]
}
# 把我们的item写入 一个txt文件a
def write_to_file(content):
with open('result.txt','a',encoding='utf-8') as f:
f.write(json.dumps(content,ensure_ascii=False)+'\n')
f.close()

def main(offset):
url = 'https://movie.douban.com/top250?start='+str(offset)+'&filter='
html = get_one_page(url)
for item in parse_one_page(html):
print(item)
write_to_file(item)

if __name__ == '__main__':
if __name__ == '__main__':
for i in range(10):
main(i * 25)
  • 结果如下

示例

用selenium + Chrome 爬取淘宝宝贝信息

所用工具: selenium包,Chrome.driver无头浏览器,pymongo+MongoDB数据库

源码一览

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import re
import time
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq


browser = webdriver.Chrome()
wait = WebDriverWait(browser, 20)
from config import *
import pymongo

client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]


def search():
try:
browser.get('http://www.taobao.com' )
input = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="q"]')))
submit = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_TSearchForm > div.search-button > button')))
input.send_keys('美食')
submit.click()
total = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > div.total')))
get_products()
return total.text
except TimeoutException:
return search()


def next_page(page_number):
try:
input = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > div.form > input')))
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit')))
input.clear()
input.send_keys(page_number)
submit.click()
wait.until(EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > ul > li.item.active > span'),str(page_number)))
get_products()
except TimeoutException:
next_page(page_number)


def get_products():
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#mainsrp-itemlist .items .item')))
html=browser.page_source
doc=pq(html)
items = doc('#mainsrp-itemlist .items .item').items()
for item in items:
product = {
'image': item.find('.pic .img').attr('src'),
'price': item.find('.price').text(),
'deal': item.find('.deal-cnt').text()[:-3],
'title': item.find('.title').text(),
'shop': item.find('.shop').text(),
'location': item.find('.location').text(),
}
print(product)
save_to_mongo(product)

def save_to_mongo(result):
try:
if db[MONGO_TABLE].insert(result):
print('存储到MONGODB成功',result)
except Exception:
print('存储到MonGoDB失败',result)

def main():
search()
total = search()
total = int(re.compile('(\d+)').search(total).group(1))
for i in range(2, total + 1):
time.sleep(10)
next_page(i)
browser.close()

if __name__ == '__main__':
main()

成果展示

示例

分步解析

搜索关键字

  • 我们这一步的目的就是模拟搜索关键字的操作
    • 找到搜索框
    • 点击搜索
  • 我们需要判断浏览器是否已经达到了我们想要的操作,要实现这一点,selenium中的wait模块可以解决Selenium官方文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
# 设置时间
element = WebDriverWait(driver, 10).until(
# 设置条件,设置要加载的目标
EC.presence_of_element_located((By.ID#这是个选择器, "myDynamicElement"))
)
finally:
driver.quit()

在我们的代码中,需要改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
wait = WebDriverWait(browser, 20)
def search():
try:
browser.get('http://www.taobao.com' )
input =
# 只要在dev_tool中在标签点击右键copy,选择类型即可
wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="q"]')))# 这里找的是输入框直到加载出来才完成操作
submit = wait.until(
# 这里找的是确定按钮,这里的条件是等到按钮能够点击,才结束
EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_TSearchForm > div.search-button > button')))
# 输入美食这个关键字
input.send_keys('美食')
submit.click()
total = wait.until(
# 这里找的是一共有多少页的显示框,条件是当这个框显示出来的时候
EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > div.total')))
# 返回我们的总页数
return total.text
# 用except捕捉这个错误
except TimeoutException:
# 如果出现了错误,我们递归一下
return search()
  • 在main函数中的操作
1
2
3
4
5
6
7
8
9
10
11
12
def main():
# 这里调用search
search()
# total接受了search()返回的内容,我们要在这里提取出总页数来
total = search()
# 利用正则表达式模块剔除汉字获得数字,在total中serach,然后类型转换为int类型
total = int(re.compile('(\d+)').search(total).group(1))
# 通过一个循环,从第二页开始一直到最后,用range实现遍历操作
for i in range(2, total + 1):
time.sleep(10)
next_page(i)
browser.close()

分析页码并翻页

  • 示例

我们可以看到,要实现这个翻页,一页一页按那个高亮按钮不太实际(每一个按钮的标签可能都不一样),所以我们在搜索框中输入第几页,然后点击确定后,再做一个判断即可完成

  • 所以我们首先要拿到第几页这个输入框,然后拿到旁边的确定按钮,最后传入数字点击确认
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def next_page(page_number):
try:
# 获取到目标输入框
input = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > div.form > input')))
# 获取到确认提交按钮
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit')))
# 首先清除框中内容
input.clear()
# 然后把这个循环对应得号码传入
input.send_keys(page_number)
# 点击确认
submit.click()
#这里做一个text_to_be_present_in_element类型得判断,就是当高亮页面标中的内容与我们传入的页数一致的时候,说明完成操作
wait.until(EC.text_to_be_present_in_element(
(By.CSS_SELECTOR, '#mainsrp-pager > div > div > div > ul > li.item.active > span'),str(page_number)))
# 最后捕捉错误信息,如果出错那么重新来过
except TimeoutException:
next_page(page_number)

分析提取商品内容

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
def get_products():
# 先做一个判断,如果这个地方通过,说明这个页面中所有的宝贝信息都已经加载好了
# 这个所有的宝贝标签都是这个标签下的子标签
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#mainsrp-itemlist .items .item')))
# 获取网页源代码信息
html=browser.page_source
# 利用pyquery解析我们的网页源代码
doc=pq(html)
# 然后把源代码这个标签下的所有
items = doc('#mainsrp-itemlist .items .item').items()# 获取所有选择的内容
# 分析每个item中的内容
for item in items:
product = {
# 找到这个标签中,属性为src的内容作为我们的图片信息
'image': item.find('.pic .img').attr('src'),
# 找到这个price标签 ,提取价格
'price': item.find('.price').text(),
# 找到.deal-cnt这个标签,然后从开头直到倒数第三个,为了截取我们想要的成交量
'deal': item.find('.deal-cnt').text()[:-3],
# 找到class为title的标签,提取标题
'title': item.find('.title').text(),
# 找到class= shop的标签,提取商家名字
'shop': item.find('.shop').text(),
# 找到class= location的标签,提取卖家地点
'location': item.find('.location').text(),
}
print(product)
  • 写完get_products操作以后,需要在一开始的search函数中调用一下(因为next_page(page_number)是从第二页开始的,所以要获取第一页的宝贝信息
  • 此外在next_page 这个函数中也要调用,来获得2-100页的所有宝贝信息

存储到MongoDB

新建一个config文件,存储一些连接到MongoDB的基本信息

1
2
3
4
5
6
# 账户时localhost
MONGO_URL = 'localhost'
# 数据库是taobao,如果没有那么会自动新建
MONGO_DB = 'taobao'
# 数据表名叫做product
MONGO_TABLE = 'product'
  • 在开头设置一下配置信息
1
2
3
4
5
import pymongo
# 设定账户
client = pymongo.MongoClient(MONGO_URL)
# 设定数据库
db = client[MONGO_DB]
  • 写一个写入函数
1
2
3
4
5
6
7
def save_to_mongo(result):
try:
# 插入到目标数据表中,并判断成功与否
if db[MONGO_TABLE].insert(result):
print('存储到MONGODB成功',result)
except Exception:
print('存储到MonGoDB失败',result)
  • 然后再get_product函数的item遍历中调用,把每条宝贝信息传入数据库

总结与反思

  • 虽然是照着视频一步一步写下来的,但是中间仍然出现了很多错误和困难

  • 淘宝网现在出了反爬虫机制,如果爬的太快我们就会受到永远都通不过的滑块验证码,这是因为淘宝检测出了你使用的webdriver,我在晚上爬取一次后换个关键词,也仍然解决不了问题(看来有冷却时间)第二天又运行一遍后才回复正常。不过我们写爬虫只是练手,并不要求高效率。所以我把等待时间调整为每次爬取一页停留十秒,这样在第一次爬取的时候可以轻松爬完100页。(再试一次就又有问题了)

  • 通过这个爬虫经历学会了如何把数据存储到MongoDB中,这样稍作修改,那么我先前爬取的豆瓣电影和猫眼电影都可以通过这种方式存储了

  • 这是两张电影排行表的最终效果示例

示例

示例

有话要说

python爬虫的故事到这里还并没有结束,只不过下面开始要进入PySpider框架和Scrapy框架的学习了,所占用的时间和空间过于繁琐,故另起炉灶。

-------------本文结束,感谢您的阅读-------------