【学习笔记】Python爬虫


页面结构介绍 - HTLM常用标签了解

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--
        table - 表格
        tr    - 行
        td    - 列
    -->
        <table width="200px" height="200px" border="1px">
            <tr>
                <td>姓名</td>
                <td>年龄</td>
                <td>性别</td>
            </tr>

            <tr>
                <td>小明</td>
                <td>18</td>
                <td>男</td>
            </tr>
        </table>
<!--    ul  li  无序列表-->
        <ul>
            <li>嘻嘻嘻</li>
            <li>哈哈哈</li>
        </ul>
<!--    ol  li  有序列表-->
        <ol>
            <li>穿衣</li>
            <li>洗漱</li>
            <li>出门</li>
        </ol>
    <a href="https://livinfly.top">我的BLOG</a>
    </body>
</html>

爬虫概念及步骤


用程序模拟浏览网页

核心

  1. 爬取网页
  2. 解析出需要的数据
  3. 爬虫和反爬虫

用途

  1. 数据分析/人工数据集
  2. 社交软件冷启动 什么是产品冷启动
  3. 舆论监控
  4. 竞争对手监控
  • 出行、社交、电商、政府

分类

  1. 通用爬虫

    • 不能根据需求准确爬取数据
  2. 聚焦爬虫

    • 确定爬取url
    • 模拟访问url,返回html
    • 解析html代码

反爬手段

  1. UA - User-Agent
  2. 代理IP
  3. 验证码访问
  4. 动态加载网页
  5. 数据加密

    urllib

  6. 定义要访问的url
  7. 模拟浏览器发送请求
  8. 解析

    基本使用

# 以baidu首页为例
import urllib.request
# 定义url https需要加上提交表单!
url = 'http://www.baidu.com'
# 模拟放松请求
response = urllib.request.urlopen(url)
# 获取页面源码
# read返回字节形式的二进制数据
# 二进制->字符串 - 解码
content = response.read().decode('utf-8')
# 打印数据
print(content)

类型与方法

import urllib.request

url = 'http://www.baidu.com'
# 模拟发送请求
# 类型为HTTPResponse
response = urllib.request.urlopen(url)
# 一个字节一个字节读取
content = response.read()
# 读取5Byte
content = response.read(5)
# 读取一行
content = response.readline()
# 按行读取完
content = response.readlines()
# 返回状态码
# 200 - 正常
print(response.getcode())
# 返回url地址
print(response.geturl())
# 获取状态信息
print(response.getheaders())

下载

import urllib.request
url_page = 'http://www.baidu.com'
# 下载网页
urllib.request.urlretrieve(url_page, 'baidu.html')
# 下载图片
url_img = 'https://tse1-mm.cn.bing.net/th/id/OIP-C.PvWSTSmMfeE_TU6ZKG7f0wHaMl?pid=ImgDet&rs=1'
urllib.request.urlretrieve(url_img, 'pic.jpg')
# 下载视频
url_video = 'https://flv.bn.netease.com/596a4dc6201a2368b0661f28d722997680dcf23e8490551a7bfbf1c6d7f3b7b73c5945c81ae446fdd35a7f2b9e8fdd2df659302a82d165c79c8c0dcf844382f71c04da26c23b00a4a101f92f1cdd3ec8aa2d1c51fee00de074e740c48d77963465959304dbdbc0dfe3f4477874b971135cc86933b157ce3a.mp4'
urllib.request.urlretrieve(url_video, 'xxx.mp4')

请求对象的定制

import urllib.request

# 单口号
# http    80
# https   443
# mysql   3306
# oracle  1521
# redis   6379
# mongodb 27017
# url组成 - 协议+主机+端口号+路径+参数( = )+锚点(#)
url = 'https://www.baidu.com'

# 被https拒了 - 伪装UA - 浏览器,检查,网络,点击url,翻到UA
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
# 请求对象的定制,urlopen不能存储字典
# 顺序为url,data,...,关键字传参
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)

get请求的quote方法

import urllib.request
import urllib.parse
# https://www.baidu.com/s?wd=%E5%91%A8%E6%9D%B0%E4%BC%A6
url = 'https://www.baidu.com/s?wd='
# 解决UA反爬
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
# 周杰伦->unicode  by urllib.parse
name = urllib.parse.quote('周杰伦')
print(name)
url = url+name
# 请求对象定制
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)

get请求的urlencode方法

应用场景:多个参数的时候

import urllib.request
import urllib.parse

base_url = 'https://www.baidu.com/s?'

data = {
    'wd':'周杰伦',
    'sex':'男',
    'location':'中国台湾省'
}

new_data = urllib.parse.urlencode(data)
url = base_url + new_data

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)

post请求 - 百度翻译

import urllib.request
import  urllib.parse

# 寻找到接口
url = 'https://fanyi.baidu.com/sug'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}

data = {
    'kw':'spider'
}
# post的请求的参数 必须要进行编码+编码(先变为str, 再变为byte)
data = urllib.parse.urlencode(data).encode('utf-8')
# print(data)
# post的请求的参数,不加在url后面,而是在请求对象定制的参数中
request = urllib.request.Request(url=url, data=data, headers=headers)
# print(request)
# 模拟发送请求
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
print(type(content))

# str -> json
import json
obj = json.loads(content)
print(obj)

# post请求的参数要[编码]
# 编码再编码(先变为str, 再变为byte)
# 参数放在请求对象定制的方法中

案例2 - 百度翻译之详细翻译

import urllib.request
import urllib.parse

# 寻找到接口
url = 'https://fanyi.baidu.com/v2transapi?from=en&to=zh'
# cookie is best!! 不同网站有不同需求(?)or 再留下UA什么的
headers = {
    'Cookie':'BIDUPSID=F685EAF03EB7C07D2AA6918F8B91F666; PSTM=1655776375; BDUSS=XlyMGRxMm1iSU56RzIyMjNkbWV6ejBLRUFabzZ-dmtySlRndnVsOWJUMlpUTmxpSVFBQUFBJCQAAAAAAAAAAAEAAACJJr9NxOO6w8Lwz8TM7MTjtcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJm~sWKZv7FiV; BAIDUID=F685EAF03EB7C07DC53B21ECE7C77EEA:SL=0:NR=10:FG=1; BDUSS_BFESS=XlyMGRxMm1iSU56RzIyMjNkbWV6ejBLRUFabzZ-dmtySlRndnVsOWJUMlpUTmxpSVFBQUFBJCQAAAAAAAAAAAEAAACJJr9NxOO6w8Lwz8TM7MTjtcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJm~sWKZv7FiV; BAIDU_WISE_UID=wapp_1656985477424_354; ZFY=yYOHI7x9G3fgtrG0wuizKjNxsXGVGtqa25mHaV6X5Z0:C; RT="z=1&dm=baidu.com&si=i4w8rva0dv&ss=l58atous&sl=2&tt=16a&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=1s7&ul=30q&hd=31m"; BA_HECTOR=0k8kal2l81002lal251hca3he14; BDRCVFR[-BxzrOzUsTb]=mk3SLVN4HKm; H_PS_PSSID=26350; BAIDUID_BFESS=53B1207E8EDBE4AA83C08A4F6075812C:FG=1; APPGUIDE_10_0_2=1; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; ab_sr=1.0.1_Y2ZlYWFhMzU4YTlkYmU0ZmU4NzAzN2E1N2VkOTE2ODk3MTcyYjdiOWU5MjlkYjU0NDMyMThjMWI5ZmZkOWU1ZDhkNDg1NmJjYmY1MTAwZjljNjk5N2Q1ZTI1NzNlZGQxMTFlNWI4NTVmMGI4OWQ4ZmZjZDM4OThhZjIzZGU3MTFhMzU3NTA3ZjI0ZmExYzYzYjA0NWY1YjUwYmIwMWU0ZTYwOTA3MmY3NjllYjBkYzI3NDJlMGEwZTdjYTQ2MWM5',
}
data = {
    'from':'en',
    'to':'zh',
    'query':'parse',
    'transtype':'realtime',
    'simple_means_flag':'3',
    'sign':'120721.341152',
    'token':'ea747f5170ff35da81e5154d352c158f',
    'domain':'common',
}
# 编码->str->byte
data = urllib.parse.urlencode(data).encode('utf-8')
# 请求对象定制
request = urllib.request.Request(url=url, data=data, headers=headers)
# 模拟请求浏览器
response = urllib.request.urlopen(request)
# 获取响应数据
content = response.read().decode('utf-8')
print(content)

import json
# str -> obj
obj = json.loads(content)

print(obj)

# 缺少完整请求头

ajax的get请求 - 豆瓣


第一页

前后端分离,所以后端返回json,再前端进行数据渲染


# get请求
# 获取豆瓣电影第一页的数据并保存
import urllib.request
import urllib.parse

url = 'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&start=0&limit=20'

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
# 请求对象定制
request = urllib.request.Request(url=url,headers=headers)
# 获取响应的数据
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
# 下载到本地 - 写到本地
# open方法默认gbk编码,想要保存别的编码使用
# encoding = 'utf-8‘
fp = open('douban.json', 'w', encoding='utf-8')
fp.write(content)
# 写完后可以格式化代码好看(快捷键ctrl+alt+L)与qq、滴答清单冲突

# 另一种形式 - 会自动关闭
with open('douban.json', 'w', encoding='utf-8') as fp:
    fp.write(content)

ajax的post请求 - 肯德基官网

X-Request-With:XMLHttpRequest -- ajax请求

# 不同的页比较一下 发现不同页数主要是pageIndex不同

import urllib.request
import urllib.parse

def design_request(page):
    url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=cname'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
    }
    data = {
        'cname':'北京',
        'pid':'',
        'pageIndex':page,
        'pageSize':'10'
    }
    data = urllib.parse.urlencode(data).encode('utf-8')
    request = urllib.request.Request(url=url,headers=headers, data=data)
    return request
def get_content(request):
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    return content
def download_data(page, content):
    with open('kfc_'+str(page)+'.json', 'w', encoding='utf-8') as fp:
        fp.write(content)

# 使得别的文件调用该文件时,不会进行下面main的代码(因为name!=main)
if __name__ == '__main__':
    start_page = int(input('输入开始页码'))
    end_page = int(input('输入结束页码'))
    for page in range(start_page, end_page+1):
        # 定制请求
        request = design_request(page)
        # 获取源码
        content = get_content(request)
        # 保存数据
        download_data(page, content)

异常

URL由协议、主机名、端口、路径、参数、锚点
URLError\HTTPError 后者时前者的子类
用try-except捕获异常

import urllib.request
import urllib.error

# url = 'https://blog.csdn.net/haojiagou/article/details/125653574'
# HTTPError
# url = 'https://blog.csdn.net/haojiagou/article/details/125653574123'
# URLError
url = 'http://www.hhahaissdifj.com'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
try:
    request = urllib.request.Request(url=url,headers=headers)
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    print(content)
except urllib.error.HTTPError:
    print('网站不存在...')
except urllib.error.URLError:
    print('2333333')

cookie登录 - 微博

适用场景:数据采集时需要绕过登录,进入某页面
个人信息页面时utf-8但还是报编码错误, 因为跳转到登录页面去了, 登陆页面不是utf-8
请求头信息不够 -> 访问不成功
cookie 携带登录信息
referer 判断防盗链-当前连接是不是由上一个路径进来 --- 一般为图片防盗链

import urllib.request

url = 'https://weibo.com/mygroups?gid=3864611478604000'
headers = {
    'cookie':'******',
    'referer':'******',
    'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49',
}
request = urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
with open('wb.html', 'w', encoding='utf-8') as fp:
    fp.write(content)

Handler处理器

为了处理如: 动态cookie\代理
定制更高级的请求头


基本使用 - 百度

import urllib.request

url = 'http://www.baidu.com'

headers = {
    'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49',
}
request = urllib.request.Request(url=url,headers=headers)

# handler  build_opener  open

# [1] 获取handler对象
handler = urllib.request.HTTPHandler()
# [2] 获取opener对象
opener = urllib.request.build_opener(handler)
# [3] 调用open方法
response = opener.open(request)

content = response.read().decode('utf-8')

print(content)

代理

常用功能:

  1. 访问国外网站
  2. 访问内网资源(大学ftp)
  3. 提高访问速度, 代理服务器有较大的硬盘缓冲区, 不同用户访问相同资源速度快
  4. 隐藏真实IP
import urllib.request

url = 'http://www.baidu.com/s?wd=ip'
headers = {
    'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49',
}
request = urllib.request.Request(url=url,headers=headers)
# 免费代理(如快代理)
proxies = {
    'http':'117.41.38.19:9000'
}
handler = urllib.request.ProxyHandler(proxies=proxies)
opener = urllib.request.build_opener(handler)
response = opener.open(request)

content = response.read().decode('utf-8')

with open('daili.html', 'w', encoding='utf-8') as fp:
    fp.write(content)

代理池

# 先找到一堆有用的代理,然后随机出来
proxies_pool = [
    {'http':'117.41.38.19:9000'},
    {'http':'202.55.5.209:8090'},
]
import random
proxies = random.choice(proxies_pool)
print(proxies)

解析


xpath

  1. 安装并启用xpath插件 --- ctrl+shift+x启动
  2. 安装lxml库(python, 安装在你的python文件的解释器处) - pip install lxml -i https://pypi.douban.com/simple (豆瓣源)

解析对象:

  1. 本地文件 --- etree.parse
  2. 服务器响应的数据 --- etree.HTML()

严格遵守html形式

基本操作

from lxml import etree
# 解析本地文件
tree = etree.parse('html1.html')
# 1. 路径查找
# tree.xpath('xpath路径') ,“//”查找所有的子孙节点  “/”直接找子节点
# 查找ul下的li
li = tree.xpath('//body/ul/li')

# 2. 谓词查询
# 查找所有有id属性的li标签
# test() 获取标签中的内容
li = tree.xpath('//ul/li[@id]/text()')
# id l1的标签
li = tree.xpath('//ul/li[@id="l1"]/text()')
# 3. 属性查询 查找id为l1的li标签的class的属性值
li = tree.xpath('//ul/li[@id="l1"]/@class')
# 4. 模糊查找
li = tree.xpath('//ul/li[contains(@id,"l")]/text()')
li = tree.xpath('//ul/li[starts-with(@id,"l")]/text()')
# 5. 内容查询 /test()
# 6. 逻辑运算
# 查询id为li,class为c1
li = tree.xpath('//ul/li[@id="l1" and @class="c1"]/text()')
li = tree.xpath('//ul/li[@id="l1" or @id="l2"]/text()')
li = tree.xpath('//ul/li[@id="l1"]/text() | //ul/li[@id="l2"]/text()')
# 最后两个等价
print(li)
print(len(li))

案例1 - 获取百度网站的百度一下

import urllib.request

# 获得源码
url = 'https://www.baidu.com'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
request = urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')

#解析
from lxml import etree

tree = etree.HTML(content)
# (类型) id是唯一的(?
# 返回的是list
result = tree.xpath('//input[@id="su"]/@value')
print(result[0])

案例2 - 站长素材

下载前十页的图片

import urllib.request
from lxml import etree

def design_request(page):
    if page == 1:
        url = 'https://sc.chinaz.com/tupian/qinglvtupian.html'
    else:
        url = 'https://sc.chinaz.com/tupian/qinglvtupian_{}.html'.format(page)
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
    }
    request = urllib.request.Request(url=url,headers=headers)
    return request
def get_content(request):
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    return content
def download_to_local(content):
    # urllib.request.urlretrieve('图片地址','文件名字')
    tree = etree.HTML(content)
    name_list = tree.xpath('//div[@id="container"]//a/img/@alt')
    # 如果len为0, 一般涉及图片的网站会设计懒加载(在加载到之前为src2,之后为src)
    # 可以直接定位到标签然后复制xpath路径
    # 使用变之前的来爬取数据!!!
    src_list = tree.xpath('//div[@id="container"]//a/img/@src')
    for i in range(len(name_list)):
        name = name_list[i]
        src = src_list[i]
        # 有些网站用'\' 可以使用url = url.replace('\\','/')
        # 用url=url.replace('_s', '')就是高清图片,即删去 _s
        url = 'https:'+src
        url = url.replace('_s', '')
        print(name, url)
        urllib.request.urlretrieve(url=url,filename='./qinglu_pic/'+name+'.jpg')

if __name__ == '__main__':
    start_page = int(input('请输入起始页码'))
    end_page = int(input('请输入结束页码'))
    for page in range(start_page,end_page+1):
        request = design_request(page)
        content = get_content(request)
        download_to_local(content)

JsonPath

能解析本地文件
教程-简单入门

基本操作

所使用json文件

{ "store": {
    "book": [
      { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      { "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}
import json
import jsonpath

# 读的是文件而非字符串
obj = json.load(open('json1.json','r',encoding='utf-8'))

# 书店所有书的作者
# '*'为通配符
author_list = jsonpath.jsonpath(obj,'$.store.book[*].author')
print(author_list)
# 所有的作者
author_list = jsonpath.jsonpath(obj,'$..author')
print(author_list)
# store下的所有元素
tag_list = jsonpath.jsonpath(obj,'$.store.*')
print(tag_list)
# store下所有的钱
price_list = jsonpath.jsonpath(obj,'$.store..price')
print(price_list)
# 第三个书
book = jsonpath.jsonpath(obj, '$..book[2]')
print(book)
# 最后一本书
book = jsonpath.jsonpath(obj, '$..book[(@.length-1)]')
print(book)
# 前两本书
book = jsonpath.jsonpath(obj, '$..book[0,1]')
# 或者[:2]
print(book)
# 过滤出所有包含isbn的书
# 条件过滤, 在 () 前面添加一个 ?
book_list = jsonpath.jsonpath(obj, '$..book[?(@.isbn)]')
print(book_list)
# 超过10$
book_list = jsonpath.jsonpath(obj, '$..book[?(@.price>10)')
print(book_list)

案例 - 解析淘票票

import urllib.request

url = 'https://dianying.taobao.com/cityAction.json?activityId&_ksTS=1657277832030_108&jsoncallback=jsonp109&action=cityAction&n_s=new&event_submit_doGetAllRegion=true'

# 这边请求头,经过测试只要有referer就可以了
headers = {
    'referer': 'https://dianying.taobao.com/index.htm?spm=5049.7840664.0.0.3ad22dbfL6fH44&n_s=new',
}

request = urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')

content = content.split('(')[1].split(')')[0]
print(content)

with open('tpp.json','w',encoding='utf-8') as fp:
    fp.write(content)
import json
import jsonpath
obj = json.load(open('tpp.json','r',encoding='utf-8'))
city_list = jsonpath.jsonpath(obj,'$..regionName')
print(city_list)

BeautifulSoup - bs4

与lxml一样是一个html的解析器
本地和响应对象都可以解析

优缺点:
    优点: 接口人性化, 使用方便(针对有基础的人)
    缺点: 效率没有lxml高

基本使用

from bs4 import BeautifulSoup

# 解析本地
# 默认打开文件 编码格式为gbk
soup = BeautifulSoup(open('html1.html',encoding='utf-8'),'lxml')

# 1.根据标签查找节点
# 找到的是第一个符合条件的数据
print(soup.a)
# 2.获取标签的属性和属性值
print(soup.a.attrs)

# 函数
# 1.find

# 返回第一个符合条件的数据
print(soup.find('a'))
# 根据title找到对应的标签对象
print(soup.find('a', title="a2"))
# class在python内部有了,+ _
print(soup.find('a',class_="a1"))

# 2.find_all

# 返回list
print(soup.find_all('a'))
# 获取多个标签,用[]
print(soup.find_all(['a','span']))
# 前2个
print(soup.find_all('li',limit=2))

# 3.select(推荐)

# 返回list
print(soup.select('a'))

# 可以用.代替class --- 这种操作--类选择器
print(soup.select('.a1'))
# #代表id
print(soup.select('#l1'))
# 有id的li
print(soup.select('li[id]'))
# li中id为l2
print(soup.select('li[id="l2"]'))

# 层级选择器

# 后代选择器
# 找到div下的li
print(soup.select('div li'))
# 子代选择器(一级子标签
# 注意:bs4中可以不用写空格
print(soup.select('div > ul > li'))
# a与li的所有的对象
print(soup.select('a,li'))

# 节点信息

# 获取节点内容
obj = soup.select(('#d1'))[0]
# 若标签对象中 只有内容, string 和 get_text() 都可以使用
# 除了内容还有标签,string获取不到数据
# 推荐get_text()
print(obj.string)
print(obj.get_text())

# 节点的属性
obj = soup.select('#p1')[0]
# 标签名字
print(obj.name)
# 属性与属性值 字典
print(obj.attrs)
# 属性值
print(obj.attrs.get('class'))
print(obj.get('class'))
print(obj['class']) # 没有该属性是报错

案例 - 星巴克图片&名字

有人分享与1一个错误,取的名字不要与导入的包一致

import urllib.request

url = 'https://www.starbucks.com.cn/menu/'
# 先直接试试有没有反爬手段
response = urllib.request.urlopen(url)
content = response.read().decode('utf-8')

from bs4 import BeautifulSoup

soup = BeautifulSoup(content,'lxml')

# //ul[@class="grid padded-3 product"]//strong/text()
# 多个并列的class可以写出3ul.grid.padded-3.product 代替空格!!!
name_list = soup.select('.grid.padded-3.product strong')
name_list = soup.select('ul[class="grid padded-3 product"] strong')

for name in name_list:
    print(name.string)
    # print(name.get_text)

Selenium

用于web应用程序测试的工具
Selenium运行在浏览器中!
支持gezhongdriver驱动
支持无界面浏览器
但是比较慢

模拟浏览器功能,自动执行网页中的js代码, 实现动态加载

ps. 教程中因为用的是老版本的selenium,所以本人采用3.1410版本


为什么学它?

如京东, 首页的秒杀数据没有!
观众补充:seckill是由js渲染出来的,js要在浏览器中运行

安装selenium

  1. 下载一个谷歌浏览器驱动 - win32就行 - 解压后放在python文件目录下就行了
  2. 谷歌驱动和谷歌浏览器之间的映射表
  3. 查看谷歌的版本 - 帮助-关于
  4. pip install selenium

    基本使用

from selenium import webdriver

# 创建浏览器操作对象
path = 'chromedriver.exe'
browser = webdriver.Chrome(path)
# 访问网站

url = 'https://www.jd.com'
browser.get(url)

# page_source - 获取网页源码
content = browser.page_source
print(content)

元素定位

模拟鼠标和键盘来操作

from selenium import webdriver

path = 'chromedriver.exe'
browser = webdriver.Chrome(path)

url = 'https://www.baidu.com'
browser.get(url)

# 元素定位

# 新版本:自行寻找
# 1.id 新版本:browser.find_element_by("id", "su")
button = browser.find_element_by_id('su')
print(button)
# 2.根据标签属性的属性值来获取对象
button = browser.find_element_by_name('wd')
print(button)
# 3.根据xpaht语句来获取对象
# 单个element(class),多个elements(list)
button = browser.find_elements_by_xpath('//input[@id="su"]')
print(button)
# 4.根据标签的名字来获取对象
button = browser.find_elements_by_tag_name('input')
print(button)
# 5.bs4语法
button = browser.find_elements_by_css_selector('#su')
print(button)
# 6.
button = browser.find_elements_by_link_text('新闻')
print(button)

元素信息

from selenium import webdriver

path = 'chromedriver.exe'
browser = webdriver.Chrome(path)

url = 'https://www.baidu.com'
browser.get(url)

input = browser.find_element_by_id('su')
# 元素属性
print(input.get_attribute('class'))
# 标签名
print(input.tag_name)
# 元素文本??
a = browser.find_element_by_link_text('新闻')
print(a.text)

交互

from selenium import webdriver

# 创建浏览器对象
path = 'chromedriver.exe'
browser = webdriver.Chrome(path)

url = 'https://www.baidu.com'
browser.get(url)

import time
time.sleep(2)

# 获取文本框的对象
input = browser.find_element_by_id('kw')
# 在文本框输入周杰伦
input.send_keys('周杰伦')
# 获取按钮
button = browser.find_element_by_id('su')
# 点击按钮
button.click()
time.sleep(2)
# 到达底部
js_bottom = 'document.documentElement.scrollTop=100000'
browser.execute_script(js_bottom)
time.sleep(2)
# 获取下一页
next = browser.find_element_by_xpath('//a[@class="n"]')
next.click()
time.sleep(2)
# 回到上一页
browser.back()
time.sleep(2)
# 再回去
browser.forward()
time.sleep(3)
# 退出
browser.quit()

phantomjs

公司已经黄了(悲
和chromedriver一样,需要先把它的exe文件导过来

无界面浏览器,运行快
因为它基本凉了,我就没有自己动手再现视频了
from selenium import webdriver

path='phantomjs.exe'
browser = webdriver.PhantomJS(path)

url = 'https://www.baidu.com'
browser.get(url)
# 无界面浏览器 快照
browser.save_screenshot('baidu.png')

import time
time.sleep(2)
input = browser.find_element_by_id('kw')
input.send_keys('周杰伦')

time.sleep(3)

browser.save_screenshot('Jay.png')

Chrome handless

chrome的一种模式
要求:chrome >= 59/60
python 3.6
selenium 3.4.*
ChromeDriver 2.31
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

# path修改为自己chrome浏览器的文件路径
path = r'C:\Program Files\Google\Chrome\Application\chrome.exe'
chrome_options.binary_location = path

# chrome_option 换成options了!!!
browser = webdriver.Chrome(options=chrome_options)

# 这上面是固定的,用的时候请直接复制,修改path即可
# 其他所有操作一致
url = 'https://www.baidu.com'
browser.get(url)
browser.save_screenshot('baidu.png')

# ---------------
# 封装的handless - 定成方法

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def share_browser():
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    # path修改为自己chrome浏览器的文件路径
    path = r'C:\Program Files\Google\Chrome\Application\chrome.exe'
    chrome_options.binary_location = path
    # chrome_option 换成options了!!!
    browser = webdriver.Chrome(options=chrome_options)
    return browser

browser = share_browser()

url = 'https://www.baidu.com'
browser.get(url)

requests

和urllib作用类似,但是部分业务用requests更简单
只属于python!
官方文档
快速上手
(这个网站好像倒了)(悲)

  1. 安装 pip install requests (可以加国内源)

基本使用

import requests

url = 'http://www.baidu.com'

response = requests.get(url=url)

# 一个类型和六个属性
# <class 'requests.models.Response'>
print(type(response))

# import urllib.request
# <class 'http.client.HTTPResponse'>
# print(type(urllib.request.urlopen(url)))

# 设置响应的编码格式
response.encoding = 'utf-8'
# 以字符串形式返回网站源码
print(response.text)
# 返回url地址
print(response.url)
# 返回二进制的数据
print(response.content)
# 返回响应的状态码
print(response.status_code)
# 返回响应头
print(response.headers)

get请求

  1. 参数用params传递
  2. 无需urlencode编码
  3. 不需要请求对象定制
  4. url里?可加可不加
import requests

# 问好可以加也可以不加
url = 'https://www.baidu.com/s?'

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
data = {
    'wd':'北京'
}

# url资源路径 params参数 kwargs字典
response = requests.get(url=url,params=data,headers=headers)
content = response.text

post请求

  1. 不需要编解码
  2. 请求的参数为data
  3. 不需要请求对象定制
import requests

# 问好可以加也可以不加
url = 'https://fanyi.baidu.com/sug'

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}

data = {
    'kw': 'eye'
}

# url, data(参数), json, kwargs(字典)
response = requests.post(url=url,data=data,headers=headers)
content = response.text

print(content)

import json

# 不要加encoding防止报错!!
obj = json.loads(content)
print(obj)

代理

我这里遇到中文乱码(悲

import requests

url = 'https://www.baidu.com/s?'

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
data = {
    'wd':'ip'
}
proxy = {
    'http':'202.55.5.209:8090'
}
response = requests.get(url=url,params=data,headers=headers,proxies=proxy)
content = response.text
print(content)

with open('daili.html','w',encoding='utf-8') as fp:
    fp.write(content)

cookie - 验证码 - 以古诗文网为例

先寻找登录接口(输入错误的密码)找到需要的参数
一般有login
_VIEWSTATE __VIEWSTATEGENEERATOR code是变量
[1]和[2],看不到的数据,一般在源码中,所以解析获取!
[3]验证码

难点

  1. 隐藏域类似__xx
  2. 验证码

    import requests
    
    # 登录页面
    url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
    
    headers = {
     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
    }
    # 获得源码
    response = requests.get(url=url,headers=headers)
    content = response.text
    
    # 参数可以在payload中找到
    # 解析获得_VIEWSTATE ...
    # 用bs4 or xpath
    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(content, 'lxml')
    
    viewstate = soup.select('#__VIEWSTATE')[0].attrs.get('value')
    viewstategenerator = soup.select('#__VIEWSTATEGENERATOR')[0].attrs.get('value')
    
    # 获取验证码图片
    code = soup.select('#imgCode')[0].attrs.get('src')
    code_url = 'https://so.gushiwen.cn'+code
    
    # 有坑!!
    # import  urllib.request
    # urllib.request.urlretrieve(url=code_url,filename='code.jpg')
    # # 前后两次请求出来的code不一样!!
    
    
    # session将请求的返回值变成一个对象
    session = requests.session()
    response_code = session.get(code_url)
    # 二进制内容
    content_code = response_code.content
    with open('code.jpg','wb')as fp:
     fp.write(content_code)
    
    code_name = input('请输入你的验证码')
    
    # preserve log勾上/不勾上 内容多不多
    
    # 点击登录
    url_post = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
    
    data_post = {
    '__VIEWSTATE': viewstate,
    '__VIEWSTATEGENERATOR': viewstategenerator,
    'from': 'http://so.gushiwen.cn/user/collect.aspx',
    'email': 'rslovesgames@qq.com',
    'pwd': 'qq123456',
    'code': code_name,
    'denglu': '登录',
    }
    response_post = session.post(url=url,headers=headers,data=data_post)
    content_post = response_post.text
    
    with open('gushiwen.html','w',encoding='utf-8')as fp:
     fp.write(content_post)

    超级鹰打码平台的使用

通过平台提供的技术来识别code

.get('pic_str')

scrapy

scrape+python=scrapy ???
提取结构性数据的应用框架


安装比较困难

记得用国内源

  1. pip install scrapy
  2. 报错

    1. 依赖的twisted没有(现在好像是会自动下载它),重安scrapy
      http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted找到对应系统的版本
    2. pip版本 - python -m pip install --upgrade pip
    3. win32错误 - pip install pypiwin32
    4. anacoda3-5.2.0 改变环境为anacoda的环境然后...

    基本使用

用终端创建项目,且不能用数字开头,不包含中文
scrapy startproject + [filename]

cd 项目文件\项目文件\spiders

创建爬虫文件 scrapy genspider [fileName] [webpageName]

运行爬虫代码 scrapy crawl [spiderName]

有一个robot协议(君子协议),改一下settings中的ROBOTSTXT_OBEY = True改掉

import scrapy

class BaiduSpider(scrapy.Spider):
    # 爬虫的名字 用于运行爬虫时候
    name = 'baidu'
    # 允许访问的域名
    allowed_domains = ['www.baidu.com']
    # 起始的url,即第一次要访问的域名
    start_urls = ['http://www.baidu.com/']
    # 执行了start_urls后 执行的方法,方法中response就是返回的对象
    # 相当于 response = requests.get() // urllib.requeset.urlopen()
    def parse(self, response):
        print('hahahahaha')

58同城 - 项目结构和基本方法

spiders 爬虫文件

init
自定义 ******

items - 定义数据结构的地方 - 爬取的数据包含哪些
middleweare - 中间件 - 代理
pipelines - 管道 - 用来处理下载的数据
settings - 配置文件 - robots协议 - ua定义等

import scrapy


class TcSpider(scrapy.Spider):
    name = 'tc'
    # tz.58.com
    allowed_domains = ['tz.58.com']
    start_urls = ['http://tz.58.com']

    def parse(self, response):
        # response.body - 二进制数据
        # response.text - str
        # content = response.text
        # print('==========================')
        # print(content)
        # 直接解析
        aaa = response.xpath('/html/body/div[3]/div[1]/div[1]/div/div[3]/div[1]/div[4]/a')[0]
        print('=============================')
        # 提取seletor对象的(data)属性值
        print(aaa.extract())
        # .extract_first() 是提取seletor列表的第一个数据

汽车之家 - 工作原理

后缀是html时,不能加/
因为网站改了+scrapy的版本不同了,也就没有本地再现

name_list = response.xpath(***/text())
price_list = ....
for i in range(len..):
    print(..,..)

scrapy的架构组成

  1. 引擎,自动运行
  2. 下载器
  3. spiders
  4. 调度器
  5. 管道

工作原理

spiders --url-> 引擎 --url-> 调度器 --请求-> 下载器
--从互联网下载数据,数据(response)-> 引擎
--数据-> spiders(通过xpath解析数据) --解析结果-> 引擎 
1. --url-> 引擎(循环)
2. --数据-> 管道(存到文件、数据库)

scrapy shell

Scrapy终端 - 免去每次修改后运行spider的麻烦
想要看到高亮,获得补全 - 安装ipython
在终端直接输入scrapy shell [webName]

然后在终端中进行和之前.py文件一样的操作

案例 - 当当网

spiders 爬虫文件
    init
    自定义 ******
items - 定义数据结构的地方 - 爬取的数据包含哪些
middleweare - 中间件 - 代理
pipelines - 管道 - 用来处理下载的数据
settings - 配置文件 - robots协议 - ua定义等

爬取数据

items.py

src = scrapy.Field()
name = scrapy.Field()
price = scrapy.Field()

dang.py

# pipelines - 下载数据
        # items - 定义数据结构
        # title = //ul[@class="bigimg"]/li/a/@title
        # src = //ul[@class="bigimg"]/li/a/img/@src
        # price = //ul[@class="bigimg"]/li/p[@class="price"]/span[1]/text()

        # 可以用range(len)来取出来
        # 所有seletor可以再次调用xpath
        li_list = response.xpath('//ul[@class="bigimg"]/li')
        for li in li_list:
            title = li.xpath('./a/@title').extract_first()
            # 图片懒加载找origanal
            # 检查数据后发现,第一张没有origanal
            src = li.xpath('./a/img/@data-original').extract_first()
            if not src:
                src = li.xpath('./a/img/@src').extract_first()
            price = li.xpath('./p[@class="price"]/span[1]/text()').extract_first()
            print(title,src,price)

管道封装

如果要使用管道,需要在settings中开启管道
settings.py

# ITEM_PIPELINES 的注释解开
# 管道可以有很多个 优先级为1->1000,值越小,优先级越高

pipelinse.py

class DangdangPipeline:
    # 在爬虫文件执行前执行
    def open_spider(self, spider):
        # print('+++++++++++++++++++++++++')
        self.fp = open('book.json','w',encoding='utf-8')

    # item就是yield后面的book对象
    def process_item(self, item, spider):
        # 以下模式不推荐,传一个对象就就开一个对象,就开一次文件,操作过于频繁!!

        # # 1.write写字符串
        # # 2.w会把它覆盖
        # with open('book.json','a',encoding='utf-8')as fp:
        #     fp.write(item)
        self.fp.write(str(item))

        return item

    # 在爬虫文件执行后执行
    def close_spider(self, spider):
        # print('------------------------')
        self.fp.close()

dang.py - 添加

from ..items import DangdangItem
# dangdang.items 会报错来着

book = DangdangItem(src=src,name=title,price=price)
            # 获取一个book就将他传给pipelines
            yield book

多条管道下载

pipelines.py - 添加

import urllib.request
# 多条管道开启
# [1] 定义管道类
# [2] 在settings中打开管道
class DangDangPipeline_Pic:
    def process_item(self,item,spider):
        url = 'http:'+item.get('src')
        filename = './books/'+item.get('name')+'.jpg'
        urllib.request.urlretrieve(url=url,filename=filename)
        return item

模仿着写
settings.py

ITEM_PIPELINES = {
   'dangdang.pipelines.DangdangPipeline': 300,
   'dangdang.pipelines.DangDangPipeline_Pic':301,
}

多页下载

观察得到网页变化规律,因为爬取逻辑一样的调用parse!

dang.py + 在parse函数中

    # 多页下载,必须要调整allowed_domains的范围 一般情况下只写域名
    allowed_domains = ['category.dangdang.com']
    start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']
    base_url = 'http://category.dangdang.com/pg'
    page = 1

    # 下面的加在parse函数正后面
        if self.page < 10:
            self.page += 1
            url = 'http://category.dangdang.com/pg{}-cp01.01.02.00.00.00.html'.format(self.page)

            # 调用parse
            # scrapy.Request是scrapy的get请求
            # url请求地址
            # callback要执行的函数,不加()
            yield scrapy.Request(url=url,callback=self.parse)

案例 - 电影天堂多页下载

先看有没有反爬(直接print======

xpath拿不到数据,检查xpath语法正确性。 span不能识别!!

# 对第二页访问
# meta字典型, 为了让name和src在一起,传进去
        ...
        yield scrapy.Request(url=url,callback=parse_second,meta={'name':name})
    def parse_second(self,response):
        # //div[@id="Zoom"]/span/img/@src
        # 拿不到数据,检查xpath语法正确性。 span不能识别!!
        src = response.xpath('//div[@id="Zoom"]//img/@src').extract_first()
        name = response.meta['name']
        movie = MovieHeavenItem(src=src,name=name)

        yield movie

CrawlSpider - 连接提取器

  • Mysql
  1. 下载
  2. 安装

pymysql

继承自scrapy.Spider

比如网站页码,可以知道链接,链接的解析规则一致

使用scrapy shell

from scrapy.linkexractors import LinkExtractor
# 正则表达式\d+ (\d 数字,+ 1-n个数字)
link = LinkExtractor(allow=r'/book/1188_\d+\.html')
link.extract_links(response)
link1 = LinkExtractor(restrict_xpaths=r'//div[@#class="pages"/a/@href')
link.extract_links(response)

# restrict_css

...

这scrapy个人感觉更偏向于企业级开发,不是特别感冒,就咕咕咕了... ^ ^

声明:残城三梦|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 【学习笔记】Python爬虫


Live In Fly && Live Infinitely