爬虫

Posted 肉丸不肉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫相关的知识,希望对你有一定的参考价值。

零、背景知识

  1. 爬虫,就是一个自动化的程序
  2. 爬虫的具体流程:网站托管在服务器上,服务器是24小时运行的。爬虫,首先会模拟请求(好像在浏览器输入网址,然后回车),爬虫可以用一些 Http 库向指定的服务器偷偷摸摸的发起请求,这个时候爬虫可以假装自己是浏览器(添加一些header信息),大多数服务器(但很多服务器建立了反爬虫机制)误以为爬虫就是浏览器,就把数据返回给爬虫了。不同的情况下,服务器返回给我们的数据格式不一样,有HTML、JSON、二进制的数据等,根据不同的情况,我们可以使用不同的方式对他们进行处理。处理完之后就可以保存。
  3. 学习的模块:
  • 抓包
  • 数据爬取
  • 数据解析
  • 数据存储
  • 数据分析:可视化
  • 爬虫框架:scrapy、
  1. 学习的教程地址

一、在chrome浏览器中进行抓包

  1. 打开一个网址后,(fn+f12)进入开发者模式。
  2. 点击“Network” 查看网络请求。
  3. HTTP 的请求方式:GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE。最常见的就是 GET 和 POST 请求。
  • GET请求
https://www.baidu.com/s?wd=%E9%83%AD%E8%89%BE%E4%BC%A6&rsv_spt=1&rsv_iqid=0xab72dcc20003b006&issp=1&f=8&rsv_bp=1&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_dl=tb&rsv_sug3=10&rsv_sug2=0&rsv_btype=i&inputT=2679&rsv_sug4=4107
  • GET 请求的参数是以 键值对 的形式出现的。
  • “?wd=%E9%83%AD%E8%89%BE%E4%BC%A6” 问号后面的是请求参数。(果真是键值对)
  • 所以以后我们在 Python 写 GET 请求的时候,直接在 URL 后面加个 ?然后添加参数值就好了
  • POST请求:做一些信息提交的时候,比如登录、注册等
  • POST 的参数不会直接放在 URL 上,会以 Form 表单的形式将数据提交给服务器
  • POST请求的参数放在request body 里面,POST请求方式还对密码参数加了密
  1. 请求头Request Header:在做 HTTP 请求的时候,除了提交一些参数之外,我们还要定义一些 HTTP 请求的头部信息。比如 Accept、Host、cookie、User-Agent等等,这些参数通过这些信息,欺骗服务器,告诉它我们是正规请求。比如可以在代码里面设置 cookie 告诉服务器我们就是在这个浏览器请求的会话,User-Agent 告诉服务器我们是浏览器请求的。
  2. 服务器的响应码:200、404、304…
  3. 响应头Response Header:是服务器返回,告诉我们数据以什么样的形式展现,告诉我们cookie的设置。
  4. 响应体:是服务器返回给我们的数据。点击 Response 就可以看到相关的数据了。获取到的数据是不一样的, 有html的,也有 JSON 的图片二进制数据等等。

二、通过 Fiddler 进行手机抓包

  1. 若想对请求的数据或者响应的数据进行篡改 或者 对手机请求的数据进行抓包:用Fiddler。
  2. Fiddler 的具体工作原理:一般情况下,通过浏览器来请求服务器的时候,是点对点的。
  • 有了Fiddler 之后,Chrome发送请求给服务器的时候,会被 Fiddler 拦截下来,可以在这里修改请求参数。
  • 然后 Fiddler 假装自己是浏览器,再发送数据给服务器。
  • 此时服务器接收到 Fiddler 的请求,以为是 Chrome 发送的,于是就返回数据了。
  • 返回的数据又被 Fiddler 拦截下来了,Fiddler 可以在这个时候,对数据进行修改,然后返回给Chrome。
  1. 有需要时再去下载使用,看细节即可。

三、Urllib 库

Python 这个内置的 Urllib 库,有4 个模块:request(用它来发起请求)、error(当我们在使用 request 模块遇到错了,可以用它来进行异常处理)、parse(用来解析我们的 URL 地址的,比如解析域名地址啦,URL指定的目录等)、robotparser(解析网站的 robot.txt)。

练习1:
import urllib.request
response = urllib.request.urlopen('http://www.baidu.com')
print(response.read().decode('utf-8'))

返回百度网址的源代码:

request 的 urlopen 方法详情:urllib.request.urlopen(url, data=None, [timeout, ]*)

  • 第一个 url 就是我们请求的链接
  • 第二个参数 data,是专门给我们 post 请求携带参数的。比如我们在登录的时候,可以把用户名密码封装成 data 传过去,在这里的 data 的值我们可以用 byte 的类型传递
  • 第三个参数 timeout 就是设置请求超时时间
练习2:添加请求头信息(request header),要欺骗服务器说我们是浏览器或者手机请求

request 模块中的 Request 方法urllib.request.Request(url, data=None, headers=, method=None)

  • 除了定义 url 和 data 之外,还可以定义请求头信息
  • urlopen 默认是 Get 请求,当我们传入参数它就为 Post 请求了。
练习三:模拟在某个网站的登录
from urllib import request, parse
import ssl  # 使用 ssl 未经验证的上下文,因为待请求的这个网站用的是https
context = ssl._create_unverified_context()

url = 'https://biihu.cc//account/ajax/login_process/'
# 定义请求头
headers = 
    # 假装自己的浏览器
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'

# 请求的参数
dict = 
    'return_url': 'https://biihu.cc/',
    'user_name': 'xxxx',
    'password': '11111',
    'post_type': 'ajax',

# 把请求的参数转化为byte
data = bytes(parse.urlencode(dict), 'utf-8')
# 封装request
req = request.Request(url, data=data, headers=headers, method='POST')

# 请求
response = request.urlopen(req, context=context)
print(response.read().decode('utf-8'))


四、Reuqests库

Requests 是在 urllib 的基础上搞出来的,通过它我们可以用更少的代码,模拟浏览器操作

但Requests库不是python的内置库,用之前需要先安装。使用pip命令安装即可。

# 导入 requests 模块
import requests

url = 'https://api.github.com/events'
# get请求
r = requests.get(url)
# post请求
r = requests.post('https://httpbin.org/post', data = 'key':'value')

# 携带请求参数
payload = 'key1': 'value1', 'key2': 'value2'
r = requests.get('https://httpbin.org/get', params=payload)

# 假装自己是浏览器
url = 'https://api.github.com/some/endpoint'
headers = 'user-agent': 'my-app/0.0.1'
r = requests.get(url, headers=headers)

# 其它乱七八糟的 Http 请求
r = requests.put('https://httpbin.org/put', data = 'key':'value')
r = requests.delete('https://httpbin.org/delete')
r = requests.head('https://httpbin.org/get')
r = requests.options('https://httpbin.org/get')

# 导入 requests 模块
import requests

url = 'https://api.github.com/events'
r = requests.get(url)

# 获取服务器响应文本内容
r.text
'''
u'["repository":"open_issues":0,"url":"https://github.com/...
'''
r.encoding
'''
'utf-8'
'''

# 获取字节响应内容
r.content
'''
b'["repository":"open_issues":0,"url":"https://github.com/...
'''

# 获取响应码
r.status_code
'''
200
'''

# 获取响应头
r.headers
'''
    
    'content-encoding': 'gzip',    
    'transfer-encoding': 'chunked',  
    'connection': 'close',    
    'server': 'nginx/1.0.4',    
    'x-runtime': '148ms',    
    'etag': '"e1ca502697e5c9317743dc078f67693f"',   
    'content-type': 'application/json'

'''

# 获取 Json 响应内容
r.json()
'''
[u'repository': u'open_issues': 0, u'url': 'https://github.com/...
'''

# 获取 socket 流响应内容
r.raw
'''
<urllib3.response.HTTPResponse object at 0x101194810>
'''
r.raw.read(10)
'''
'\\x1f\\x8b\\x08\\x00\\x00\\x00\\x00\\x00\\x00\\x03'
'''
Post请求:当想要一个键里面添加多个值的时候




五、正则表达式

通过正则表达式:过滤出我们想要的内容。(在服务器返回给我们的源码之中,仅拿到我们想要的数据即可。)

正则表达式不仅仅适用于 python,很多编程语言,很多地方都会使用到正则。

比如:如何从下面这段字符串中快速检索所有的数字出来呢?

zui12shu234ai45der6en7sh88ixia7898os0huaib

综上,正则表达式就是定义一些特殊的符号,来匹配不同的字符

1. 各种符号的解释

贼多。记不住。

2. 最常用的匹配


3. 用 python 来进行判断:用python 的re库

使用这个方法:re.match。主要传入两个参数,第一个就是我们的匹配规则,第二个就是需要被过滤的内容

import re

content = 'Xiaoshuaib has 100 bananas'
# 将我们需要的目标内容用 () 括起来
res = re.match('^Xi.*(\\d+)\\s.*s$', content)
print(res.group(1))
'''
输出0
'''

res = re.match('^Xi.*?(\\d+)\\s.*s$', content)
print(res.group(1))
'''
输出100
'''
  • 贪婪匹配 vs. 非贪婪匹配:贪婪匹配是一个数一个数都要去匹配,而非贪婪直接把 100 给匹配出来了。
  • .*?匹配任意字符
  • .代表所有的单个字符,除了 \\n \\r
表达式 .* 就是单个字符匹配任意次,即贪婪匹配。 
表达式 .*? 是满足条件的情况只匹配一次,即最小匹配(非贪婪模式)

当待匹配的字符串有换行时,需要用到re的匹配规则:re.S

content = '''Xiaoshuaib has 100 
bananas'''

res = re.match('^Xi.*?(\\d+)\\s.*s$', content, re.S)
print(res.group(1))

'''
输出100
'''

匹配规则中,不写字符串开头和结尾:re.search:直接去扫描字符串,然后把匹配成功的第一个结果的返回给你。

content = '''Xiaoshuaib has 100 
bananas'''

res = re.search('Xi.*?(\\d+)\\s.*s', content, re.S)
print(res.group(1))

'''
输出100
'''

当想获取所有的100数字,用re.findall:获取所有匹配的内容

content = """Xiaoshuaib has 100 bananas;
Xiaoshuaib has 100 bananas;
Xiaoshuaib has 100 bananas;
Xiaoshuaib has 100 bananas;
"""

# 注意此时匹配规则改了
res = re.findall('Xi.*?(\\d+)\\s.*?s', content, re.S)
print(res)

'''
输出['100', '100', '100', '100']
'''

想直接替换匹配的内容,用re.sub

content = """Xiaoshuaib has 100 bananas;
Xiaoshuaib has 100 bananas;
Xiaoshuaib has 100 bananas;
Xiaoshuaib has 100 bananas;
"""

# 匹配规则变化了
res = re.sub('\\d+', '250', content)
print(res)

'''
输出:
Xiaoshuaib has 250 bananas;
Xiaoshuaib has 250 bananas;
Xiaoshuaib has 250 bananas;
Xiaoshuaib has 250 bananas;
'''
res = re.sub('Xi.*?(\\d+)\\s.*?s', '250', content)
print(res)

'''
输出:
250;
250;
250;
250;
'''

把我们的匹配符封装一下,用re.compile:便于以后复用

content = "Xiaoshuaib has 100 bananas"

pattern = re.compile('Xi.*?(\\d+)\\s.*s', re.S)
res = re.match(pattern, content)
print(res.group(1))

'''
输出:100
'''

六、使用 requests 库和 re 库来写一个爬虫:爬取当当网的前 500 本好五星评书籍

1. 首先要对待爬取的目标网站进行分析

每页有25条数据,每页的url最后的变量不同。如下图所示:

第一页:

第二页:

2. 分析要拿到每个项目的哪些关键数据,看在什么标签内(用正则表达式来过滤)


通过服务器返回的源码可以发现,待提取的数据信息被放在了li标签中。

3. 保存在数据库/文件中

主要思路:

  • 使用 page 变量来实现翻页
  • 我们使用 requests 请求当当网
  • 然后将返回的 HTML 进行正则解析
  • 保存在数据库/文件中
import json
import re
import requests


def request_dangdang(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
    except requests.RequestException:
        return None


def parser_result(html):
    pattern = re.compile('<li>.*?list_num.*?(\\d+).</div>.*?<img src="(.*?)".*?class="name".*?title="(.*?)">.*?class="star">.*?class="tuijian">(.*?)</span>.*?class="publisher_info">.*?target="_blank">(.*?)</a>.*?class="biaosheng">.*?<span>(.*?)</span></div>.*?<p><span\\sclass="price_n">&yen;(.*?)</span>.*?</li>', re.S)
    items = re.findall(pattern, html)
    for item in items:
        yield 
            'range': item[0],
            'image': item[1],
            'title': item[2],
            'recommend': item[3],
            'author': item[4],
            'times': item[5],
            'price': item[6]
        


def write_item_to_file(item):
   print('开始写数据 ===> ' + str(item))
   with open('book.txt', 'a', encoding='UTF-8') as f:
       f.write(json.dumps(item, ensure_ascii=False) + '\\n')
       f.close()


def main(page):
    url = 'http://bang.dangdang.com/books/fivestars/01.00.00.00.00.00-recent30-0-0-1-' + str(page)
    html = request_dangdang(url)
    # 解析过滤我们想要得到的关键信息
    items = parser_result(html)

    for item in items:
        # 把爬到的本书的关键数据都依次存到文件中
        write_item_to_file(item)


if __name__ == "__main__":
    for i in range(1, 26):
        main(i)


七、一个高效的网页解析库:BeautifulSoup(可代替正则表达式来快速的获取我们想要的内容)

此库的中文文档

1. 安装BeautifulSoup库

2. 安装解析器

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml。

3. 开始使用

一段HTML代码:

html_doc = """

<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>

"""

使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup 的对象

按照标准的缩进格式的结构输出:

from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

soup = BeautifulSoup(html_doc, 'lxml')
print(soup.prettify())

输出:

几个简单的浏览结构化数据的方法:

soup.title
# <title>The Dormouse's story</title>

soup.title.name
# u'title'

soup.title.string
# u'The Dormouse's story'

soup.title.parent.name
# u'head'

soup.p
# <p class="title"><b>The Dormouse's story</b></p>

soup.p['class']
# u'title'

soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

soup.find_all('a')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.find(id="link3")
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

从文档中找到所有a标签的链接:

for link in soup.find_all('a'):
    print(link.get('href'))
    # http://example.com/elsie
    # http://example.com/lacie
    # http://example.com/tillie

从文档中获取所有文字内容:

print(soup.get_text())
# The Dormouse's story
#
# The Dormouse's story
#
# Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well.
#
# ...

八、使用 requests库和beayutifulsoup库 来爬取豆瓣最受欢迎的250部电影

1. 首先看不同页数之间的page变量规律



2. 分析想要获取的每个item的关键数据以及源代码标签定位:



3. 主要思路:
  • 请求豆瓣的链接获取网页源代码
  • 然后使用 BeatifulSoup 拿到我们的内容
  • 最后就把数据存储到 excel 文件
from bs4 import BeautifulSoup
import requests
# excel的库
import xlwt


def request_douban(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
    except requests.RequestException:
        return None


book = xlwt.Workbook(encoding='UTF-8', style_compression=0)
sheet = book.add_sheet('豆瓣电影Top250', cell_overwrite_ok=True)
sheet.write(0, 0, '名称')
sheet.write(0, 1, '图片')
sheet.write(0, 2, '排名')
sheet.write(0, 3, '评分')
sheet.write(0, 4, '作者')
sheet.write(0, 5, '简介')


def save_to_excel(soup):
    items 以上是关于爬虫的主要内容,如果未能解决你的问题,请参考以下文章

NIH周三讲座视频爬虫

Python练习册 第 0013 题: 用 Python 写一个爬图片的程序,爬 这个链接里的日本妹子图片 :-),(http://tieba.baidu.com/p/2166231880)(代码片段

python爬虫学习笔记-M3U8流视频数据爬虫

爬虫遇到头疼的验证码?Python实战讲解弹窗处理和验证码识别

python网络爬虫

在学习爬虫的路上,有多少坑在前边