Python爬虫笔记-scrapy,selenium,xpath,requests等,爬取淘宝B站等案例2024年5月13日更新

前言:亲爱的小伙伴们,你们通过百度搜索找到本文章的还是从B站评论来的呢?如果你是从B站评论来的,那么恭喜你,这个评论也是一个python的selenium小项目自动发送的。地址为:视频地址。希望对大家的学习有所帮助。这个小项目的源代码也在下面的本地pdf笔记里面哦,欢迎大家下载学习(代码仅供学习参考使用)。

本笔记从零基础开始(不含python)从urllib到selenium再到xpath,bs4,requests,scrapy框架。根据哔哩哔哩尚硅谷以及千峰教育的视频教程综合学习归纳总结,并且对于视频教程中过时内容进行了自主学习,笔记成型于2024年5月9日。所有案例均可实现(网络视频教学中很多已经不适用现在的框架了)。可以免去同学们学习过程中解决错误所浪费的大量时间精力。希望能对各位同学有所帮助。

从0到实战,内含多个实战案例,最终结合selenium,scrapy框架,xpath等实现淘宝网的爬取。

可本地下载,适合小白学习。如果链接失效请联系,由于这里的笔记是直接复制本地文件,所以图片内容在本地故图片无法展示,pdf中的则是完整内容,内含xpath插件以及爬取淘宝案例教学。需要的小伙伴可以下载百度网盘中的pdf到本地。下载地址:网盘链接 

urllib

基础操作

urllib是python内置对于url操作的库

import urllib.request
​
url = 'http://www.baidu.com'
​
# 模拟浏览器向服务器发送请求
response = urllib.request.urlopen(url)
res = response.read().decode('utf-8')
print(res)

一个类型六个方法

import urllib.request
​
url = "http://www.baidu.com"
​
# 模拟浏览器向服务器发送请求
response = urllib.request.urlopen(url)
​
# HTTPResponse类型
# print(type(response))
​
# 一个字节一个字节读
# content = response.read()
# print(content)
​
# 返回多少个字节
# content = response.read(10)
# print(content)
​
# 读取一行
# content = response.readline()
# print(content)
​
# 读取一行知道读完
# content = response.readlines()
# print(content)
​
# 返回状态码
print(response.getcode())
​
# 返回url地址
print(response.geturl())
​
# 返回响应头
print(response.getheaders())

下载内容

import urllib.request

# 下载网页
url_page = 'http://www.baidu.com'

# 两个参数,url地址和name
# urllib.request.urlretrieve(url_page, 'baidu.html')

# 下载图片
# urllib.request.urlretrieve(url='https://img0.baidu.com/it/u=4171982618,1877587227&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',filename='lisa.jpg')


# 下载视频
url = 'https://f.video.weibocdn.com/o0/1cwUEIpulx08e6gn721W01041200vkh70E010.mp4?label=mp4_720p&template=1280x720.25.0&media_id=5023422250418218&tp=8x8A3El:YTkl0eM8&us=0&ori=1&bf=4&ot=h&ps=3lckmu&uid=3ZoTIp&ab=,8013-g0,3601-g39&Expires=1713321712&ssig=B6AD8OUGE0&KID=unistore,video'
urllib.request.urlretrieve(url, filename='zjj.mp4')

请求对象的定制

https协议具有UA(user-agent)反爬

import urllib.request

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

# url的组成
# http/https
#  协议
#  http 80 https 443
# redis 6379
# mongodb 27017
# oracle 1521

# https://www.baidu.com/s?wd = 周杰伦

# http/https    www.baidu.com       80/443   s  wd = 周杰伦  #
# 协议                主机            端口    路径  参数     锚点
# 返爬user-agent不完整
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'}
# 定制request
request = urllib.request.Request(url=url, headers=headers)

response = urllib.request.urlopen(request)
print(response.read().decode('utf8'))

编解码

百度的时候我们输入中文进行检索,但是我们将浏览器地址复制下来发现中文变成一串代码

https://www.baidu.com/s?wd=%E5%91%A8%E6%9D%B0%E4%BC%A6
https://www.baidu.com/s?wd=周杰伦
如果我们使用中文在urllib爬取会出现报错
UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-13: ordinal not in range(128)

8ff66aaf130219224c576b28865fbbbc

urllib.parse.quote方法

该方法用于将中文转换成unicode编码

使用urllib.parse中的方法quote('汉字')对中文进行转换成unicode编码格式

import urllib.parse

parse_chinese = urllib.parse.quote("张三")
print(parse_chinese) # 输出结果:%E5%BC%A0%E4%B8%89
import urllib.request
import urllib.parse

url = 'https://www.baidu.com/s?wd=%E5%91%A8%E6%9D%B0%E4%BC%A6'
# 模拟浏览器发送请求
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

# 对中文进行转换unicode
name = urllib.parse.quote('周杰伦')
url = url + name

# 请求对象的定制
# Request定制
request = urllib.request.Request(url=url, headers=headers)

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

print(content)
urllib.parse.urlencode方法

该方法用于多个中文参数转换成unicode

import urllib.parse

data = {
    'wd': '周杰伦',
    'sex': '男'
}

encodes = urllib.parse.urlencode(data)
print(encodes)
wd=%E5%91%A8%E6%9D%B0%E4%BC%A6&sex=%E7%94%B7
# urlencode应用场景:多参数转换unicode

# import urllib.parse

# data = {
#     'wd': '周杰伦',
#     'sex': '男',
#     'local': '台湾'
# }
#
# encodes = urllib.parse.urlencode(data)
# print(encodes)

# 获取https://www.baidu.com/s?wd=%E5%91%A8%E6%9D%B0%E4%BC%A6&sex=%E7%94%B7&local=%E5%8F%B0%E6%B9%BE网页源代码

import urllib.request
import urllib.parse

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

data = {
    'wd': '周杰伦',
    'sex': '男',
    'local': '台湾'
}
new_data = urllib.parse.urlencode(data)
# 请求资源路径
url = base_url + new_data
print(url)

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}
# 请求对象的定制

request = urllib.request.Request(url=url, headers=headers)
# 模拟浏览器向服务器发送请求
response = urllib.request.urlopen(request)

content = response.read().decode('utf-8')
print(content)
爬虫发送post请求

post请求必须进行编码,编码之后还得调用encode('utf-8')

import urllib.request
import urllib.parse

url = 'https://fanyi.baidu.com/sug'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

data = {
    'kw': 'spider'
}
# post请求的参数,必须进行编码
data = urllib.parse.urlencode(data).encode('utf-8')
print(data)

# post请求的参数,是不会拼接在url的后面的,而是需要放在请求对象定制的参数中
request = urllib.request.Request(url=url, data=data, headers=headers)

# 模拟浏览器向服务器发送请求
response = urllib.request.urlopen(request)
# 获取响应数据
content = response.read().decode('utf-8')
import json

obj = json.loads(content)
print(obj)
# post 请求方式的参数必须编码  data = urllib.parse.urlencode(data).encode('utf-8')
# 编码之后必须调用encode方法
# 参数是放在请求对象定制的方法中,request = urllib.request.Request(url=url, data=data, headers=headers)

post请求百度之详细翻译

解决cookie反爬

urllib爬取ajax—get请求更新发送过来的数据

下载豆瓣电影的数据

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

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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

# 定制request
request = urllib.request.Request(url=url, headers=headers)
# 获取响应数据
response = urllib.request.urlopen(request)

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

# 将数据下载到本地,默认使用的是gbk的编码,需要在open中指定编码格式为utf8
# fp = open('豆瓣.json', 'w', encoding='utf-8')
# fp.write(context)
# 另一种形式
with open("豆瓣.json", 'w', encoding="utf-8") as fp:
    fp.write(context)

爬取豆瓣前10页的电影信息

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

# page  1  2  3  4
# start 0 20  40 60
# start(page-1)*20


# 下载豆瓣电影前10页的电影


# 1、请求对象定制

# 2、获取响应的数据

# 3、下载数据

import urllib.parse
import urllib.request


def create_request(page):
    base_url = 'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&'

    data = {
        'start': (page - 1) * 20,
        'limit': 20
    }

    data = urllib.parse.urlencode(data)
    url = base_url + data
    print(url)
    headers = {
        'User-Agent': ' Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
    }
    # 请求对象定制
    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 down_load(page, content):
    with open("douban" + str(page) + ".json", 'w', encoding="utf-8") as fd:
        fd.write(content)


# 程序入口
if __name__ == '__main__':
    start_page = int(input("请输入起始的页码"))
    end_page = int(input("请输入结束的页码"))

    for page in range(start_page, end_page + 1):
        request = create_request(page)
        # 获取响应数据
        content = get_content(request)
        # 下载
        down_load(page, content)
处理ajax-post请求发送的数据

爬取肯德基地址前10页的数据,肯德基的网页是通过点击下标post请求上传给服务器,返回的数据。

image-20240424173505365

# cname: 北京
# pid:
# pageIndex: 1
# pageSize: 10
#
# cname: 北京
# pid:
# pageIndex: 2
# pageSize: 10


import urllib.request
import urllib.parse


def get_request(page):
    url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=cname'
    data = {
        'cname': '北京',
        'pid': '',
        'pageIndex': page,
        'pageSize': '10'
    }
    data = urllib.parse.urlencode(data).encode('utf-8')
    headers = {
        'User-Agent': ' Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
    }
    request = urllib.request.Request(url=url, data=data, headers=headers)
    return request


def get_content(request):
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    return content


def down_load(content, page):
    with open("肯德基\kfc" + str(page) + ".json", 'w', encoding='utf-8') as fd:
        fd.write(content)


if __name__ == '__main__':
    start_page = int(input("请输入起始页码"))
    end_page = int(input("请输入结束页码"))

    for page in range(start_page, end_page + 1):
        request = get_request(page)
        content = get_content(request)
        down_load(content, page)

URLError\HTTPError

img

import urllib.request
import urllib.error

# url = 'https://blog.csdn.net/Xixi0864/article/details/1378732571'
url = "https://www.goudan111.com"
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}
try:
    request = urllib.request.Request(url=url, headers=headers)
    response = urllib.request.urlopen(request)
    print(response.read().decode('utf-8'))
except urllib.request.HTTPError:  # 当出现HTTPError错误时
    print("系统正在升级...")
except urllib.request.URLError:  # 当出现URLError错误时
    print("系统正在升级懂吗?")

urllib中的cookie登录

一些数据必须是登录之后才能看到的数据。如果我们使用python爬取链接的时候需要加上headers中的cookie,这是一种简单的防止爬取的方式。

import urllib.request

url = 'https://www.wsy.com/search.htm?accurate=&site_id=1&search_type=item&q=%E7%9A%AE%E8%A1%A3'
headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
   'cookie': 'cna=78L8HXFghBkCASoxyFNlAWst; xlly_s=1; thw=cn; lgc=tb02155003; dnk=tb02155003; ...'
}


request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
with open("网商园.html", 'w', encoding='utf-8') as fd:
    fd.write(content)

Handler处理器

我们之前学习,如果有user-agent校验的情况就需要请求头定制。
为什么学习handler:
		Handler可以定制更高级的请求头(随着业务逻辑的复杂,请求对象的定制已经满足不了我们的需求(动态cookie和代理不能使用请求对象的定制)
    
    动态cookie:每次登录cookie都不一样。
# 使用handler访问百度,获取网页源代码

import urllib.request

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

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

request = urllib.request.Request(url=url, headers=headers)
# handler build_opener open
# 获取handler对象
handler = urllib.request.HTTPHandler()

# 获取opener对象
opener = urllib.request.build_opener(handler)
# 调用open方法
response = opener.open(request)

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

代理服务器

img

# 使用handler访问百度,获取网页源代码

import urllib.request

url = 'https://www.ip138.com/'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

request = urllib.request.Request(url=url, headers=headers)

# response = urllib.request.urlopen(request)

# handler builder_opener  open
proxies = { # 代理IP地址
    'http': '59.54.238.36:18010'
}
# 创建handler
handler = urllib.request.ProxyHandler(proxies=proxies)# 传入参数是IP的字典
opener = urllib.request.build_opener(handler) # 创建opener
response = opener.open(request) 

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

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

IP代理池

import urllib.request

# 一个简易的代理池里面有很多ip
proxies = [
    {'http': '59.54.238.36:18010'},
    {'http': '59.54.238.36:23565'},
]
import random

proxies = random.choice(proxies)

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

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}
request = urllib.request.Request(url=url, headers=headers)
handler = urllib.request.HTTPHandler()  # 获取handler
opener = urllib.request.build_opener(handler)  # 获取opener
response = opener.open(request)
content = response.read().decode('utf-8')
with open("dialichi.html", 'w', encoding='utf-8') as fd:
    fd.write(content)

html解析

xpath解析
xpath:
    注意:提前安装xpath插件
    	pip install lxml -i http://pypi.douban.cam/simple
    导入lxml.etree
    	from lxml imoprt etree
    etree.parse() 解析本地文件
    	html_tree=etree.parse('xx.html')
    etree.HTML()服务器响应文件
    	html_tree= etree.HTML(response.read().decode('utf-8'))
    html_tree.xpath(xpath路径)
xpath插件安装

给chrome浏览器安装xpath插件。

  • 从网盘下载xpath的插件文件

image-20240425204721410

  • 在chrome中输入chrome://extensions/打开扩展程序。

image-20240425204843253

  • 将文件.zip文件直接拖到浏览器中

image-20240425204922163

  • 得到chrome插件,将插件开关开启,并且打开开发者模式。

image-20240425205001046

  • 重启浏览器,按住shift+ctrl+x检查是否安装成功

    image-20240425210110742

安装lxml库
pip install lxml -i https://pypi.douban.com/simple

xpath语法
from lxml import etree

# xpath解析
# (1)本地文件 etree.parse()
# (2)服务器响应的数据 response.read().decode('utf-8') etree.HTML()


# xpath解析本地文件
tree = etree.parse('4爬虫_xpath解析.html')

# xpath基本语法
# tree.xpath('xpath路径')

# 查找ul下面的li
# 1. 路径查询
# // :表示查询所有子节点,不考虑层级
# / : 表示查询直接子节点
li_list = tree.xpath("//body//ul/li")
li_list1 = tree.xpath("//body//li")
print(li_list1)
print(len(li_list))

# 2.过滤查找
# 查找ul下所有的带有id的li标签
# 语法: tree.xpath('//ul/li[@id]')
# text():获取标签中的内容
li_list = tree.xpath('//ul/li[@id]/text()')
print(li_list)

# 找到所有id为l1的li标签
li_list = tree.xpath('//ul/li[@id="l1"]/text()')
print(li_list)

# 查找id为l1的li标签的class属性的属性值
li_list = tree.xpath('//ul/li[@id="l1"]/@class')
print(li_list)

# 3.模糊查询
# 查询id里面包含l的li标签,l不一定在开头
li_list = tree.xpath('//ul/li[contains(@id,"l")]')
print(li_list)
li_list = tree.xpath('//ul/li[contains(@id,"l")]/text()')
print(li_list)

# 查询id的值以l开头的li标签
li_list = tree.xpath('//ul/li[starts-with(@id,"c")]/text()')
print(li_list)

# 查询id为l1和class为c1的数据
li_list = tree.xpath('//ul/li[@id="l1" and @class ="c1"]/text()')
print(li_list)

# 4、逻辑运算
li_list=tree.xpath("//ul/li[@id ='l1']/text() | //ul/li[@id='l2']/text()")
print(li_list)
xpath和lxml以及urllib的综合使用-爬取下载图片
from lxml import etree
import urllib.request
#  1 获取网页源码
# 2 解析 解析服务器文件 tree.HTML()
#  3 打印
import urllib.request

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

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

request = urllib.request.Request(url=url, headers=headers)

# 模拟浏览器访问
response = urllib.request.urlopen(request)

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

# 解析网页源码,获取想要的数据.解析服务器文件
tree = etree.HTML(content)

# 获取想要的数据
result = tree.xpath('//input[@id="su"]/@value')[0]

print(result)

下载站长素材中的图片

# 请求对象的定制
import urllib.request
from lxml import etree

url = 'https://sc.chinaz.com/tupian/meinvtupian_2.html'
url = 'https://sc.chinaz.com/tupian/meinvtupian_3.html'


def getRequest(page):
    url = None
    if page == 1:
        url = 'https://sc.chinaz.com/tupian/meinvtupian.html'
    else:
        url = 'https://sc.chinaz.com/tupian/meinvtupian_' + str(page) + '.html'

    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
    }
    request = urllib.request.Request(url=url, headers=headers)
    return request


# 获取网页源码
def getHTMLResource(request):
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    return content


# 下载
def down_load(content):
    tree = etree.HTML(content)
    name_list = tree.xpath("//div[@class ='container']//div/img/@alt")
    src_list = tree.xpath("//div[@class ='container']//div/img/@data-original")
    for i in range(len(name_list)):
        name = name_list[i]
        src = src_list[i]
        url = 'https:' + src
        print(name, url)
        urllib.request.urlretrieve(url, filename='./美女图片/' + name + '.jpg')


if __name__ == '__main__':
    start_page = int(input("请输入起始页"))
    end_page = int(input("请输入结束页"))
    for page in range(start_page, end_page + 1):
        request = getRequest(page)
        content = getHTMLResource(request)
        img = down_load(content)
        # print(img)

Jsonpath解析

用于解析json数据,jsonpath只能解析本地文件。xpath适用于解析HTML,Jsonpath解析适用于json的解析

教程:JSONPath-简单入门_jsonpath简单入门-CSDN博客

jsonpath安装:
	pip install jsonpath
jsonpath的使用:
	obj= json.load(open('json文件','r',encoding='utf-8'))
    ret = jsonpaht(obj,'jsonpath语文')

image-20240427134731063

案例:解析json
{
  "store": {
    "book": [
      {
        "category": "修真",
        "author": "六道",
        "title": "坏蛋是怎么炼成的",
        "price": 9.89
      },
      {
        "category": "修真",
        "author": "天蚕土豆",
        "title": "斗破苍穹",
        "price": 19.89
      },
      {
        "category": "修真",
        "author": "唐家三少",
        "title": "斗罗大陆",
        "isbn": "0-553-21311-3",
        "price": 2.89
      },
      {
        "category": "修真",
        "author": "南派三叔",
        "title": "星辰变",
        "isbn": "0-553-19395-3",
        "price": 5.89
      }
    ],
    "bicycle": {
      "author": "那笔",
      "color": "黑色",
      "price": 19.2
    }
  }
}
jsonpath语法
import jsonpath
import json

# 解析json
# 书店中所有的数
obj = json.load(open('7爬虫_解析_jsonpath.json', 'r', encoding='utf-8'))
type_list = jsonpath.jsonpath(obj, '$.store.book[*].author')
# print(type_list)

# 所有作者
all_author = jsonpath.jsonpath(obj, '$..author')
# print(all_author)

# store下面的所有元素
tag_list = jsonpath.jsonpath(obj, '$.store.*')
# print(tag_list)

# store下面的所有的price
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]')
book = jsonpath.jsonpath(obj, '$..book[:2]')
# print(book)

# 条件过滤要在()前面加?
# 过滤出所有包含版本号的书
book_list = jsonpath.jsonpath(obj, '$..book[?(@.isbn)]')
# print(book_list)

# 哪本书超过10块钱
book = jsonpath.jsonpath(obj, '$..book[?(@.price>10)]')
# print(book)

jsonpath解析淘票票网站
import urllib.request

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

headers = {
    'accept': 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01',
    # 'accept-encoding': ' gzip, deflate, br',
    'accept-language': 'zh-CN,zh;q=0.9',
    'bx-v': ' 2.5.11',
    'cookie': 'thw=cn; sgcookie=E100osBnkPrEtW0P6zOPyvUiaU084F5dctl3C36uHPNsaso%2F8J%2FvWdzxpSmjcbKDQijE%2BSpnofz%2BkxSw5FMpfDZ4C8aw0trVG%2Bxj8Dv%2B6CGeAK4%3D; havana_lgc_exp=1713142392623; wk_cookie2=1f2f67081a95c218ba17032d7abe4e0b; wk_unb=VyySV2N2PSBr4A%3D%3D; env_bak=FM%2BgyPYhUxUOKlrAEisf0v9cPvpEIoahBGVvaGCsLVHk; t=57fbd0772a83ecde5c26da42faf866d1; mt=ci=0_0; tracknick=; cna=78L8HXFghBkCASoxyFNlAWst; 3PcFlag=1714044772178; tfstk=f1HSCYZ-2ab5Enob-7K4hu_kYvwC949ZyMZKjDBPv8eJA2gtPWPL8Q0QO2u09Yyr4qMI7qtnaWyJA2gtPWPKTvrKdq28tbzUzywIxVt27dJZq023eF-2QAugYqe3JpyLrToySJLw7KleKwh8pXoLEyFjDrqQJkBKJiFYok6d92U8MZE_jJUKJJKbHlqR2aCRempYb3FhfkO7r0TF165Iqg-pnrBdpYp3NPifT9BKeuNSD0UjV0MbV7aTgRa1oY3qvYkuiitYKmlsRX39u_z-1kiZO0pRH8ko2fFKBefKq8zSJWGD5Or7UjNsFbxhk03aPfeoYGTrcWhqE5DykTwts0yP4FBa5B2dtUyOdoawcn1hT1GaqmYFbvDuwoq7gntfyXV8moawcn1ht7E0Vl-Xcahh.; cookie2=126be53a7b6c407e1735df9ca72d6b41; v=0; _tb_token_=e84535396eabe; xlly_s=1; isg=BBoatde4KDhNo6SRsaC8ReCIa8A8S54l3aYUkCSTpK14l7rRDNltNaXpZ2MLcxa9',
    'referer': 'https://dianying.taobao.com/',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-origin',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
    'x-requested-with': 'XMLHttpRequest'
}
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]
with open("8爬虫_jsonpath_解析淘票票.json", 'w', encoding="utf-8") as fd:
    fd.write(content)

import json
import jsonpath

obj = json.load(open("8爬虫_jsonpath_解析淘票票.json", 'r', encoding="utf-8"))
city_list = jsonpath.jsonpath(obj, '$..regionName')
print(city_list)
BeautifulSoup

e7147472ad70e718266b78f61b0693ce

f871138d2eff8fd5182cd2f3e1ad7099

f871138d2eff8fd5182cd2f3e1ad7099

爬虫案例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>网页标题</title>
</head>
<body>
<h1>标题1</h1>
<h2>标题2</h2>
<h3>标题3</h3>
<h4>标题4</h4>
<a href="http://www.python.com">python</a>
<div id="content" class="default">
    <p>段落</p>
    <a href="http://www.baidu.com">百度</a>
    <a href="http://www.crazyant.com">疯狂蚂蚁</a>
    <a href="http://www.iqiyi.com">爱奇艺</a>
    <img src="https://zhekou-background.manmanbuy.com/image/zhekou/20231108/1d720dddedcf4614ac5992c259e8559b.jpg?x-oss-process=image%2Fformat%2Cjpg%2Fresize%2Cw_500%2Fquality%2Cq_80" alt="">
</div>
</body>
</html>

BeautifulSoup使用

from bs4 import BeautifulSoup

with open("./test.html", encoding='utf-8') as fin:
    html_doc = fin.read()

soup = BeautifulSoup(html_doc, "html.parser")

links = soup.find_all("a")
for link in links:
    print(link.name, link["href"], link.get_text())

img = soup.find("img")
print(img['src'])
print("*" * 10)
div_node = soup.find('div', id="content")
links_1 = div_node.find_all('a')
for link in links_1:
    print(link)

1、BeautifulSoup简称:
	bs4
2、BeautifulSoup和lxml一样,是一个html的解析器,主要功能也是解析和提取数据
3、优缺点:
	缺点:效率没有lxml高
    优点:接口人性化设计,使用方便
安装:
	pip install bs4
导入:
	from bs4 import BeautifulSoup
创建对象:
	服务器响应的文件生成对象
    soup=BeautifulSoup(response.read().decode(),"lxml")
    本地文件生成对象
    	soup = BeautifulSoup(open('1.html'),'lxml')
        注意:默认打开我呢间的编码格式gbk所需要指定打开编码格式

BeautifulSoup的基本语法

测试的html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <ul>
        <li id="l1">张三</li>
        <li id="l2">李四</li>
        <li>王五</li>
        <li>赵六</li>
        <a href="" id="" class="a1">尚硅谷</a>
        <span>hahaha</span>
    </ul>
</div>
<a href="" title="a2">百度</a>
</body>
</html>

语法:

from bs4 import BeautifulSoup

# 通过解析本地文件
# 默认打开的文件的编码格式gbk,需要指定编码
soup = BeautifulSoup(open("9爬虫_解析_BeautifulSoup.html", encoding='utf-8'), "lxml")
# 根据标签名查找节点
# 找到的是第一个符合条件的标签
# print(soup.a)
# 获取标签的属性和属性值
# print(soup.a.attrs)

# bs4的一些函数
# find: 返回第一个符合条件的数据
# print(soup.find("a"))
# 根据title的值找到对应的标签对象
# print(soup.find("a",title ="a2"))
# 根据class的值找对应的标签对象,class需要下划线
# print(soup.find('a',class_ = 'a1'))


# find_all:返回一个列表,并且返回所有的a的标签
print(soup.find_all("a"))
# 如果想获取多个标签数,在find_all中添加的是列表的数据
print(soup.find_all(['a', 'span']))

# limit的作用是查找前几个数据
print(soup.find_all("li", limit=2))

# select(推荐)
# select方法返回一个列表 并且会返回多个数据
# print(soup.select('a'))

# 可以通过. 代表class,这种操作叫做类选择器
print(soup.select('.a1'))

# 通过#代表id,选择器选择id为l1的标签
print(soup.select("#l1"))

# 属性选择器,通过属性来寻找对应的标签
# 查找li标签中有id的标签
print(soup.select("li[id]"))

# 查找li标签中id为l2的标签
print(soup.select('li[id= "l2"]'))

print("=============后代选择器==============")
# 层级选择器
# 后代选择器
# 找到div下面的li  空格 表示后代 div li
print(soup.select("div li"))
print("============子代选择器==============")
# 子代选择器
# 某标签的第一级子标签
# 注意:很多计算机编程语言中如果不加空格不会输出内容,但是bs4中不会报错,会显示内容div > ul > li
print(soup.select("div>ul>li"))

print("==========找到a标签和li标签的所有的对象===========")
# 找到a标签和li标签的所有的对象
print(soup.select("a,li"))
节点信息

获取节点的信息,比如内容,属性等

html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <ul>
        <li id="l1">张三</li>
        <li id="l2">李四</li>
        <li>王五</li>
        <li>赵六</li>
        <a href="" id="" class="a1">尚硅谷</a>
        <span>hahaha</span>
    </ul>
</div>
<a href="" title="a2">百度</a>
<div id="d1">
    <span>
        哈哈哈
    </span>
</div>
<span id="s1">李四</span>
<p id="p1" class="p2">呵呵呵</p>
</body>
</html>

案例

# 节点信息
#  获取节点内容
obj = soup.select("#d1")[0]  # <div id="d1"><span>哈哈哈</span></div>
obj1 = soup.select("#s1")[0]  # <span id="s1">李四</span> 这个标签中只有内容,没有子标签,可以使用string属性
# 如果标签对象中,只有内容,那么string和get_text()都可以使用
# 如果标签对象中,除了内容还有标签,那么string获取不到数据,而get_text()可以
# 推荐get_text()
print(obj.get_text())
print(obj1.string)

# 节点的属性
obj = soup.select("#p1")[0]
# name是标签的名字
print(obj.name)
# 将属性值作为一个字典返回
print(obj.attrs)

# 获取节点的属性
obj = soup.select("#p1")[0]
# print(obj.attrs.get('class'))
# print(obj.get('class'))
print(obj['class'])

案例-bs4爬取网商园
import urllib.request
from bs4 import BeautifulSoup

url = 'https://www.wsy.com/search.htm?accurate=&site_id=1&search_type=item&q=T%E6%81%A4'
# 这个cookie是自己从浏览器中复制下来的。
headers = {
    'cookie': 'CATEGORYPID-SITE_ID:1=30; HOMEPTYPE-SITE_ID:1=30; UM_distinctid=18f0fd2c56ce0-02bba1091bf484-3e604809-1fa400-18f0fd2c56ded; _ati=4722370614660; first_visited=1; cart_num=0; oauthtype=100; city_temp=%E6%9D%AD%E5%B7%9E; _wsy_=b25c0232b7e1b2760b76507380a3b303; usertype=1; tracknick=手机号; _BASEUSID=MTI5OTI5NzE1MzI4MDM0NTk3; search_history_goods=; LAST_SEARCH=%5B%22%E7%9A%AE%E8%A1%A3%22%2C%22T%E6%81%A4%22%2C%22%E7%89%9B%E4%BB%94%E8%A3%A4%22%5D; CNZZDATA1257041518=1091661087-1713957422-https%253A%252F%252Fwww.baidu.com%252F%7C1714266823; SERVERID=d2b88d7de3d56f8387c7530b41d19380|1714266823|1714266653',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
soup = BeautifulSoup(content, 'lxml')
# //div[@class='item-c']//div[@class='tit']/a/text()
name_list = soup.select('div[class = "item-c"] div[class = "tit"]>a')
# print(name_list)
for name in name_list:
    print(name.get_text().strip())

Selenium

Selenium是什么?
	Selenium是一个用于web应用程序的测试工具
    直接运行在浏览器中,像真用户在操作一样
    支持通过各种driver驱动真实浏览器完成测试
    selenium也支持无界面浏览器操作
为什么使用Selenium?
	模拟浏览器功能,自动执行网页中的js代码,实现动态加载1
如何安装Selenium
	操作谷歌浏览器驱动下载地址
    	淘宝镜像:https://registry.npmmirror.com/binary.html?path=chromedriver/
        官方镜像:https://sites.google.com/a/chromium.org/chromedriver/downloads
        https://getwebdriver.com/
        https://getwebdriver.com/chromedriver#stable
    	http://chromedriver.storage.googleapis.com/index.html
    谷歌驱动和浏览器版本之间的映射
    	https://blog.csdn.net/qq_34562959/article/details/88572333
    查看谷歌版本:右上角--帮助--关于
    pip install  selenium
    
    
    Firefox浏览器驱动下载地址:


    Edge浏览器驱动下载地址:
    这是windows下的浏览器
    
    
    异常处理
	https://www.selenium.dev/documentation/webdriver/troubleshooting/errors/driver_location/
    
    

selenium基本使用

WebDriver | Selenium selenium官网

【Python爬虫 • selenium】selenium4新版本使用指南_selenium4教程-CSDN博客 学习

八千字直接带你学完《基于Python的Selenium4从入门到高级》全教程-腾讯云开发者社区-腾讯云 使用

selenium4之后不需要手动指定驱动的路径到python文件中,但是需要将chrome添加到环境变量中。

image-20240428103910122

image-20240428202503909

selenium的演示

import time
# 导入selenium包
from selenium import webdriver

# 加载指定页面并关闭
# 打开浏览器
browser = webdriver.Chrome()

# 指定加载页面
url = "https://www.jd.com/"
browser.get(url)

# 设置定时5秒
time.sleep(5)

#关闭浏览器
browser.quit()
元素定位
  • find_element()系列:用于定位单个的页面元素。

  • find_elements()系列:用于定位一组页面元素,获取到的是一组列表。

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 打开指定浏览器
browser = webdriver.Chrome()

# 打开指定页面
# browser.get("https://www.baidu.com/")

# 获取title
# print(browser.title)

# 通过id属性获取输入框
# input_text = browser.find_element(By.ID, 'kw')
# 向搜索框中输入内容
# input_text.send_keys("selenium")


# 通过name属性选择文件文本输入框元素,并设置内容
# browser.find_element(By.NAME, 'wd').send_keys("selenium")

# 通过id属性获取“百度一下”按钮,并执行点击操作
# browser.find_element(By.ID, 'su').click()

# 通过class属性定位
# 通过class获取百度的输入框并写入内容
# browser.find_element(By.CLASS_NAME, 's_ipt').send_keys("张三")

# 通过tag定位
# tag是标签名,定位到的标签不一定是唯一的
# browser.get("https://blog.csdn.net/")
# a = browser.find_element(By.TAG_NAME, "button").click()


# 通过link定位
# link表示包含有属性href的标签元素,如:linktext可以通过LINK_TEXT进行定位。
# browser.get("https://www.baidu.com/")
# LINK_TEXT表示的是链接文本,通过链接文本定位
# <a href="http://news.baidu.com">新闻</a>
# browser.find_element(By.LINK_TEXT, "新闻").click()

# By.PARTIAL_LINK_TEXT模糊定位,通过超链接中的文本模糊查询
browser.get("https://www.baidu.com/")
# 完整链接为<a href="http://news.baidu.com">新闻</a>
browser.find_element(By.PARTIAL_LINK_TEXT, "新").click()

# 停留5秒后进入下一步
time.sleep(6)
# 关闭浏览器
browser.quit()
通过xpath定位
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 启动浏览器并打开指定页面
browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")

# 通过xpath定位输入框,输入内容
browser.find_element(By.XPATH, '//input[@id="kw"]').send_keys("张三")

# 通过xpath获取百度一下按钮,click
browser.find_element(By.XPATH, '//input[@id="su"]').click()

# 定时
time.sleep(5)
# 关闭
browser.quit()
通过css定位
• find_element(By.CSS_SELECTOR,'XX')根据元素的css选择器来完成定位,可以准确定位任何元素,但需要熟练掌握css选择器
• css选择器
1. 类选择器--------.XXX选择class属性为xxx的元素
2. id选择器-------- #XXX选择id属性为xxx的元素
3. 元素选择器-----XXX选择标签名为xxx的元素
4. 属性选择器-----[yyy='bbb']选择yyy属性取值为bbb的元素
5. 派生选择器-----AA>XX或AA XX选择AA标签下的XX元素
• 你可以通过获取xpath的方式来从页面获取css选择器在css里标识层级关系使用的是>或者空格(xpath里使用的是/) div#xx1>input.yy2

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 启动浏览器并打开指定页面
browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")

"""
1. 类选择器--------.XXX选择class属性为xxx的元素
2. id选择器-------- #XXX选择id属性为xxx的元素
3. 元素选择器-----XXX选择标签名为xxx的元素
4. 属性选择器-----[yyy='bbb']选择yyy属性取值为bbb的元素
5. 派生选择器-----AA>XX或AA XX选择AA标签下的XX元素
"""

# 使用css的方式来选择,#表示id,.表示类
browser.find_element(By.CSS_SELECTOR, "#kw").send_keys("张三")
time.sleep(3)
browser.find_element(By.CSS_SELECTOR, "#kw").clear()
# browser.find_element(By.CSS_SELECTOR, '#su').click()

# 类选择器选择
a = browser.find_element(By.CSS_SELECTOR, '.c-icon')
print(a)
# 定时
time.sleep(5)
# 关闭
browser.quit()
文本的输入清除和提交
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 启动浏览器并打开指定页面
browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")

# input_text = browser.find_element(By.ID, "kw")
# 输入文本
# input_text.send_keys("张三")
# time.sleep(2)
# 清除文本
# input_text.clear()

"""
submit()提交
• 注意:submit()只能用于包含属性type='submit'的标签,并且嵌套在form表单中。
• 也可以使用click()代替submit()使用。
• 注意:submit()和click()是有很大区别的,这里不再做具体说明。
"""

browser.find_element(By.XPATH, '//input[@id = "kw"]').send_keys("张三")
submit_tag = browser.find_element(By.XPATH, '//input[@id = "su"]')
submit_tag.submit()# 提交
# 定时
time.sleep(5)
# 关闭
browser.quit()

获取页面内容
获取页面内容
• title页面标题
• page_source 页面源码
• current_url页面连接
• text标签内文本
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 启动浏览器并打开指定页面
browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")

# 获取网页标题
print(browser.title)
# 获取网页源代码
# print(browser.page_source)
# 获取网页链接
print(browser.current_url)
# 获取标签内文本
a = browser.find_element(By.XPATH,'//div[@id="s-top-left"]/a[1]')
print(a.text)

# 定时
time.sleep(5)
# 关闭
browser.quit()

隐式等待

.implicitly_wait(N)通过一定时长等待页面元素加载,最大等待时长N秒,如果中间某个时刻元素加载好了,就会结束等待,执行下一步操作;如果超出设置时间元素没加载出来,抛出没有这样的元素异常。

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 启动浏览器并打开指定页面
browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")

# 隐示等待
browser.implicitly_wait(5)
# 关闭

browser.quit()
time.sleep(3)

调整浏览器尺寸
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 启动浏览器并打开指定页面
browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")

"""
maximize_window()窗口最大化。
• minimize_window()窗口最小化。
• set_window_size(width,height)调整窗口到指定尺寸。
"""
# browser.maximize_window()
# browser.minimize_window()
browser.set_window_size(100,200)

time.sleep(3)
# 关闭
browser.quit()
获取元素信息
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 启动浏览器并打开指定页面
browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")
input_value = browser.find_element(By.ID, 'kw')

# 获取元素的属性值
attr1 = input_value.get_attribute("class")
attr2 = input_value.get_attribute("name")
print(attr1)
print(attr2)

# 获取元素的标签名
print(input_value.tag_name)

# 获取元素文本
a = browser.find_element(By.LINK_TEXT, "新闻")
print(a.text)

time.sleep(3)
# 关闭
browser.quit()
交互
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 启动浏览器并打开指定页面
browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")

# 在百度文本框中输入内容
input_tag = browser.find_element(By.ID, "kw")
input_tag.send_keys("周杰伦")
time.sleep(2)

# 获取百度一下的按钮
button = browser.find_element(By.XPATH, '//input[@id="su"]')
time.sleep(2)

# 点击按钮
button.click()

time.sleep(2)

# 滑到底部
js_button = "document.documentElement.scrollTop=100000"
browser.execute_script(js_button)

# 获取下一页的按钮
next_button = browser.find_element(By.CLASS_NAME, "n")
# 下一页
next_button.click()

time.sleep(3)
# 浏览器截图生成图片
browser.get_screenshot_as_file("tset.png")


# 刷新页面
browser.refresh()

time.sleep(3)
# 回退到上一页
browser.back()

time.sleep(2)
# 前进一页
browser.forward()

time.sleep(3)
# 关闭
browser.quit()

Phantomjs
什么是Phantomjs?
	是一个无界面浏览器
    支持页面元素查询,js的执行等
    由于不进行css和gui渲染,运行效率要比真实浏览器更快
已经黄了

Chrome handless

是Chrome浏览器针对浏览器59版本新增的一种模式,可以在不打开UI界面的情况下使用Chrome浏览器,运行效果和Chrome保持一致

必须chrome>60
python3.6以上
Selenium=3.4
chrmeDriver=2.31
配置
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# chromehandles的前置配置
# 创建操作项目
chrome_options = Options()
# 设置Chrome为Headless模式
chrome_options.add_argument('--headless')
# 禁用GPU加速
# 有些程序不支持GPU加速或者操作系统不兼容,会导致浏览器无法正常启动。此时,我们可以通过添加--disable-gpu参数来避免这些问题。
chrome_options.add_argument('--disable-gpu')
# 指定浏览器地址(放弃)
# path = r'C:\Program Files\Google\Chrome\Application\chrome.exe'
# 设置浏览器地址(放弃)
# chrome_options.binary_location = path

# 创建Chrome WebDriver对象,传入ChromeOptions对象
browser = webdriver.Chrome(options=chrome_options)  # chrome_options已经被options替代

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

browser.get(url)
# 输出源码,如果控制台有输出则表示成功
print(browser.page_source)

封装handles

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


# 封装handles
def get_handles():
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    browser = webdriver.Chrome(options=chrome_options)
    return browser


if __name__ == '__main__':
    browser = get_handles()

    browser.get("http://www.baidu.com")
    print(browser.page_source)

requests库

requests是和urllib一样的作用。

文档:
	官方文档:https://requests.readthedocs.io/projects/cn/zh-cn/latest/
    快速上手:https://requests.readthedocs.io/projects/cn/zh-cn/latest/user/quickstart.html
安装:
	pip install requests
requests基本使用
import requests

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

response = requests.get(url=url)

# 设置响应编码格式
response.encoding = 'utf-8'

# 一个类型和六个属性
print(type(response))

# 以字符串形式返回网页源码
print(response.text)

# 返回url地址
print(response.url)

# 返回二进制数据
print(response.content)

# 返回状态码
print(response.status_code)

# 返回响应头信息
print(response.headers)

# 返回cookie
print(response.cookies)

requests_get请求
import requests

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

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

data = {
    'wd': '北京'
}

response = requests.get(url=url, params=data, headers=headers)

content = response.text
print(content)

# 参数使用params传递
# 参数不需要编码
# 不需要请求对象定制
# 请求资源中的路径中的?可以+也可以不加
requests_post请求
import requests

url = 'https://fanyi.baidu.com/sug'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}
data = {
    'kw': 'eye'
}
response = requests.post(url, data=data, headers=headers)
# 方式1:
content = response.json()

# 方式2
# content = response.text
# import json
# content = json.loads(content)


print(content)

# post请求不需要编解码
# post请求的参数是data
# 不需要请求对象的定制
requests代理
import requests

url = 'https://qifu-api.baidubce.com/ip/local/geo/v1/district?'
proxy = {
    'http': '202.101.213.246:16331'

}

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

response = requests.get(url=url, headers=headers, proxies=proxy)
content = response.text

print(content)
案例-图片验证码登录爬取
# 通过登录 进入主页面
import requests
from bs4 import BeautifulSoup
import ddddocr

"""
__VIEWSTATE: Mv2wWuOcKQjHhibEDggO2pdOqBFxk3NHo1tj+fAORgQgGvFk+5JdZR9tTCMb1pFwD0DSWcsy3xcnY2orOqrvIHcHMVq65HnzqsekMMogkxGRFzDiSAKb8bpFShCGcbnAFYpGQiBfL25DFJQWeylM8dqaUSs=
__VIEWSTATEGENERATOR: C93BE1AE
from: http://so.gushiwen.cn/user/collect.aspx
email: 自己的@qq.com
pwd: 密码
code: XE31
denglu: 登录
"""
# 登录页面url地址
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; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
}

response = requests.get(url=url, headers=headers)
content = response.text

# 解析页面源码获取__VIEWSTATE  __VIEWSTATEGENERATOR
soup = BeautifulSoup(content, "lxml")
state_value = soup.select("#__VIEWSTATE")[0].attrs.get('value')
state_generator_value = soup.select("#__VIEWSTATEGENERATOR")[0].attrs.get('value')

# 获取验证码图片
code = soup.select("#imgCode")[0].attrs.get('src')
code_url = 'https://so.gushiwen.cn/RandCode.ashx' + code
print(code_url)

# 有坑
# import urllib.request
# urllib.request.urlretrieve(url=code_url, filename="code.jpg")
# requests中有一个方法session(),通过session的返回值,就能使请求变成一个对象
session = requests.session()
# 验证码url的内容
response_code = session.get(code_url)
# 此时要使用二进制数据
content_code = response_code.content
# wb模式就是将二进制数据写入到文件
with open("code.jpg", 'wb') as fd:
    fd.write(content_code)

# 获取验证码的图片之后,下载到本地,在控制台输入验证码,然后将这个值给code参数就可以登录了


# code_name = input("请输入你的验证码:")

ocr = ddddocr.DdddOcr()
with open('code.jpg', 'rb') as f:
    img_bytes = f.read()
code_name = ocr.classification(img_bytes)
print('识别出的验证码为:' + code_name)

# url_post = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
# 这是post请求需要发送的参数,从浏览器复制下来
data_post = {
    '__VIEWSTATE': state_value,
    '__VIEWSTATEGENERATOR': state_generator_value,
    'from': " http://so.gushiwen.cn/user/collect.aspx",
    'email': "邮箱@qq.com",
    'pwd': '密码',
    '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 fd:
    fd.write(content_post)

7476a87a3f308e0ac6cfc5db85b27150

2e10afd2e2295646dbc9432589a7301b

6a749056c55182c4478edec02d85fcdd

url管理器

587b5b01c067aa5d7592132f119f2b0d

ddddocr库

ocr:光学字符识别。这是一个开源的第三方库,可以使用图片验证码

案例地址https://blog.csdn.net/weixin_58839230/article/details/124243584
项目地址:https://github.com/sml2h3/ddddocr
import ddddocr

ocr = ddddocr.DdddOcr()
with open('code.jpg', 'rb') as f:
    img_bytes = f.read()
res = ocr.classification(img_bytes)
print('识别出的验证码为:' + res)

scrapy框架

教程:

从原理到实战,一份详实的 Scrapy 爬虫教程-CSDN博客

scrapy是什么?
	scrapy是一个未来爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘,信息处理或者存储历史数据等一系列的程序中
安装scrapy
	pip install scrapy
    

img

scrapy基本使用

创建scrapy项目

在命令行中输入:
scrapy startproject 项目名
项目名:不能使用数字开头,不能包含中文

创建的文件目录结构:

image-20240429211330157

创建爬虫文件
在scrapy生成的项目中进入扫spiders文件夹创建爬虫文件
cd 项目名\项目名\spiders


创建爬虫文件
scrapy genspider 爬虫文件的名字 要爬取的网页
scrapy genspider baidu http://www.baidu.com
    
    生成的文件:
    
import scrapy


class BaiduSpider(scrapy.Spider):
    # 爬虫的名字
    name = "baidu"
    # 允许访问的域名
    allowed_domains = ["www.baidu.com"]
    # 起始的url地址,指的是第一次访问的域名
    start_urls = ["http://www.baidu.com"]

    # 是执行了start_url之后执行的方法,方法中的response就是返回的那个对象
    # 相当于response= urllib.request.urlopen()
    def parse(self, response):
        print("张三是我")

# 运行爬虫代码
#  scrapy crawl 爬虫的名字  (需要进入到spiders目录下)
# scrapy crawl baidu

# 爬取失败,因为有/robots.txt协议
# 在settings.py中进行注释
# ROBOTSTXT_OBEY = True
scrapy项目的结构

image-20240429211330157

项目名字

	项目名字

		spider文件夹(存储爬虫文件)

				init

				自定义的爬虫文件(重要)

		init

		items  定义数据结构的地方 爬取的数据都包含哪些

		middleware  中间件,  代理

		pipelines      管道  用来处理下载的数据

		settings  	配置文件  robots协议  ua定义等

image-20240430142308141

response的属性和方法
response.text  获取的是响应的字符串
response.body  获取的是二进制数据
response.xpath  可以直接xpath方法来解析response中的内容
response.extract() 提取seletor对象那个的data属性值<class 'scrapy.selector.unified.Selector'>
response.extract_first() 提取seletor列表的第一个数据 <class 'scrapy.selector.unified.SelectorList'>
selector.get()现在get()方法也可以返回列表的第一个数据,get()是selector的方法
    def parse(self, response):
        # 字符串
        # content = response.text
        # print(content)
        # 二进制数据
        # b = response.body
        # print(b)
        span = response.xpath('//div[@id="filter"]/div[@class="tabs"]/a/span')[0]
        print("&"*20)
        print(type(span)) #<class 'scrapy.selector.unified.Selector'>
        print(span) 现在直接输出span也是返回内容,和extract()输出的内容一样。
        print(span.extract())

采集汽车之家

    def parse(self, response):
        a = response.xpath('//div[@id="tab2-1"]/dl[5]/dd/a[@name="a_struct"]/text()')  # 这里查出来的是一个集合
        print("#" * 30)
        print(type(a))
        print(a.extract_first())  # 获取selectorlist的第一个数据
        for i in range(len(a)):
            name = a[i].extract()
            print("名字是:", name)
scrapy工作原理

img

img

scrapy shell
什么是scrapy shell?
	scrappyshell是一个交互式shell,您可以在其中快速调试 scrape 代码,而不必运行spider。它本来是用来测试数据提取代码的,但实际上您可以使用它来测试任何类型的代码,因为它也是一个常规的Python外壳。

shell用于测试xpath或css表达式,并查看它们是如何工作的,以及它们从您试图抓取的网页中提取的数据。它允许您在编写spider时交互地测试表达式,而不必运行spider来测试每个更改。

https://www.osgeo.cn/scrapy/topics/shell.html
    
使用scrapy shell
命令行界面中输入scrapy shell 域名
进入到界面中直接使用response
response.body
response.text
response.xpath('xx')

scrapy当当网案例

爬取当当网图片的图片,价格,标题信息

items 定义数据结构的地方 爬取的数据都包含哪些

items.py

items.py文件是用于定义数据结构,就是指要爬取的东西是哪些
class ScrapyDang33Item(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 就是要下载的东西有什么

    # 图片
    src = scrapy.Field()
    # 名字
    name = scrapy.Field()
    # 价格
    price = scrapy.Field()

在主程序中导入items的类

import scrapy
from .. import items




class DangSpider(scrapy.Spider):
    name = "dang"
    allowed_domains = ["bang.dangdang.com"]
    start_urls = ["http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-24hours-0-0-1-1"]

    def parse(self, response):
        # pipelines 下载数据
        src = "//ul[@class='bang_list clearfix bang_list_mode']/li//img/@src"
        alt = "//ul[@class='bang_list clearfix bang_list_mode']/li//img/@alt"
        price = "//ul[@class='bang_list clearfix bang_list_mode']/li/div[@class='price']/p/span[1]/text()"
        # 所有selector对象都可以再次调用xpath对象
        li_list = response.xpath("//ul[@class='bang_list clearfix bang_list_mode']/li")
        print("(((((((((((((((((((((((((((((((")
        for li in li_list:
            print(type(li))  # <class 'scrapy.selector.unified.Selector'>
            src = li.xpath(".//img/@src").extract_first()
            name = li.xpath(".//img/@alt").extract_first()
            price = li.xpath(".//div[@class='price']/p/span[1]/text()").extract_first()
            book = items.ScrapyDang33Item(src=src, name=name, price=price)
            # 将这个对象交个pipelines下载
            yield book

yield关键字作用

在 Scrapy 中,yield 语句用于生成一个 Request 或 Item 对象,并将其提交给 Scrapy 引擎进行处理。这个对象可以是一个需要下载的新页面的请求(Request),或者是从当前页面中提取的数据条目(Item)。

具体来说,在爬虫代码中,yield 通常用于以下两个方面:

在解析函数中生成 Item 对象,用于将爬取到的数据保存到数据库或者文件中。

在解析函数中生成 Request 对象,用于构造下一次请求。当爬虫解析完一个页面后,需要根据页面内容构造出下一次请求并发送给服务器。这时可以使用 yield 语句生成新的 Request 对象,并将其交给Scrapy 引擎处理。


1.带有yield的函数不是一个普通函数,而是一个generator,可以用来迭代
2.yield是一个类似return的关键字,迭代一次遇到yield时就返回yield后面的值。重点:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行
3.简要理解:yield就是一个return 返回一个值,并且记住这个返回的位置,下一次迭代就从这个位置后(下一行)开始:

yield关键字将对象交给管道了,我们需要打开管道,需要在settings中开启管道。(解开注释)

ITEM_PIPELINES = {
   #  管道是可以有很多个的,那么挂到是有优先级的1-1000,值越小优先级越高
   "scrapy_dang_33.pipelines.ScrapyDang33Pipeline": 300,
}

pipelines.py

该文件用于下载

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter


# 如果想使用管道必须在settings中开启
class ScrapyDang33Pipeline:
    # 在爬虫文件开始之前执行的方法(方法名是固定的)
    def open_spider(self, spider):
        self.fd = open('book.json', 'a', encoding='utf-8')

    # item就是yield后面传递过来的对象
    def process_item(self, item, spider):
        # 以下模式不推荐,使用文件操作过去频繁
        # print("种类是pipelines:", type(item))
        # # write方法必须写入一个字符串而不是其他对象
        # with open('book.json', 'a', encoding='utf-8') as fd:
        #     fd.write(str(item))
        #     return item
        self.fd.write(str(item))


    # 在爬虫文件执行之后开始执行的
    def close_spider(self, spider):
        self.fd.close()

多管道下载

多管道下载就是在单管道的基础上增加class作为管道类,定义方法process_item,这个方法框架自己调用。

管道1
# 如果想使用管道必须在settings中开启
class ScrapyDang33Pipeline:
    # 在爬虫文件开始之前执行的方法
    def open_spider(self, spider):
        self.fd = open('book.json', 'a', encoding='utf-8')

    # item就是yield后面传递过来的对象
    def process_item(self, item, spider):
        # 以下模式不推荐,使用文件操作过去频繁
        print("种类是pipelines:", type(item))
        # # write方法必须写入一个字符串而不是其他对象
        # with open('book.json', 'a', encoding='utf-8') as fd:
        #     fd.write(str(item))
        #     return item
        self.fd.write(str(item))
        return item

    # 在爬虫文件执行之后开始执行的
    def close_spider(self, spider):
        self.fd.close()


import urllib.request
管道2

# 开启多条管道
#   定义管道类
#   在settings中开启管道
# "scrapy_dang_33.pipelines.ScrapyDangDang33Pipeline": 301,
class DangDangloadPipeline:
    def process_item(self, item, spider):
        print("item的信息是:", item)
        url = item.get('src')
        filename = item.get('name')
        filename = './图片/'+filename + '.jpg'
        urllib.request.urlretrieve(url=url, filename=filename)
        return item

在settings中开启管道

ITEM_PIPELINES = {
   #  管道是可以有很多个的,那么挂到是有优先级的1-1000,值越小优先级越高
   "scrapy_dang_33.pipelines.ScrapyDang33Pipeline": 300,
   # 开启:DangDangloadPipeline
   "scrapy_dang_33.pipelines.DangDangloadPipeline": 301,
}

多页数据的下载-分页式

在主程序中定义

不同的页面地址只是略微不同,稍微修改一下url之后重复执行parse即可完成

class DangSpider(scrapy.Spider):
    name = "dang"
    # 如果是多页下载,必须调整allowed_domains的范围,一般只写域名
    allowed_domains = ["bang.dangdang.com"]
    start_urls = ["http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-24hours-0-0-1-1"]
    base_url = "http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-24hours-0-0-1-"
    page = 1

    def parse(self, response):
        # pipelines 下载数据
        src = "//ul[@class='bang_list clearfix bang_list_mode']/li//img/@src"
        alt = "//ul[@class='bang_list clearfix bang_list_mode']/li//img/@alt"
        price = "//ul[@class='bang_list clearfix bang_list_mode']/li/div[@class='price']/p/span[1]/text()"
        # 所有selector对象都可以再次调用xpath对象
        li_list = response.xpath("//ul[@class='bang_list clearfix bang_list_mode']/li")
       
        for li in li_list:
            print(type(li))  # <class 'scrapy.selector.unified.Selector'>
            src = li.xpath(".//img/@src").extract_first()
            name = li.xpath(".//img/@alt").extract_first()
            price = li.xpath(".//div[@class='price']/p/span[1]/text()").extract_first()
            # 将我们爬取下来的数据封装成对象ScrapyDang33Item
            book = items.ScrapyDang33Item(src=src, name=name, price=price)
            print("爬取下来的数据:", type(book))
            # 将这个对象交个pipelines下载
            yield book

        #             每一页的爬取单的业务逻辑都是用于的,我们只需要将执行那个页的请求再次调用parse方法就可以
        # http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-24hours-0-0-1-1
        # http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-24hours-0-0-1-2
        # http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-24hours-0-0-1-3
        if self.page < 25:
            self.page = self.page + 1
            url = self.base_url + str(self.page)
            #             怎么调用parse方法
            #             scrapy.Request就是scrapy的get请求
            #             url就是请你地址,page就是要执行的函数
            yield scrapy.Request(url=url, callback=self.parse)
豆瓣爬取多页数据
from typing import Iterable

import scrapy
from scrapy import Selector, Request # 第二种方式要导包

from scrapy_douban.items import MoveItem


class DoubanSpider(scrapy.Spider):
    name = "douban"
    allowed_domains = ["movie.douban.com"]
    start_urls = ["https://movie.douban.com/top250"]
    #方式2
    #去掉start_urls,定义一个函数,函数返回每一个要爬取的url的地址,这样起始页就不是一个而是一组。我们将每一组的页面都执行		parse函数中的操作。得到的就是250条电影榜单的数据
       def start_requests(self) -> Iterable[Request]: # ->定义返回值类型
        for page in range(10):
            yield Request(url=f'https://movie.douban.com/top250?start={page * 25}&filter=')

    # def start_requests(self):
    #     for page in range(10):
    #         yield Request(url=f'https://movie.douban.com/top250?start={page * 25}&filter=')


    def parse(self, response):
        sel = Selector(response)
        list_item = sel.xpath("//div[@class='item']/div[@class='pic']")
        for li in list_item:
            rank = li.xpath('./em/text()').get()
            title = li.xpath("./a/img/@alt").get()
            pic = li.xpath("./a/img/@src").get()
            move = MoveItem(rank=rank, title=title, pic=pic)
            yield move
        #     解析新的url,方式1
        #这种方式是在每一页的下面找到链接地址,得到每一页的链接地址,并将这个链接封装成Request返回,由框架处理。
        #框架不会处理已经爬取过的地址,但是一般情况下第一页都有两个地址,所以会多爬取一次。第二种方式可以解决这种问题。
        # href_list = sel.xpath("//div[@class='paginator']/a")
        # for href in href_list:
        #     href = href.xpath('./@href').get()
        #     url = response.urljoin(href)
        #     yield Request(url=url)

电影天堂多页数据下载-嵌套式

我们先建立好项目:网站名字叫做樱花动漫。

我们的需求是爬取首页的图片以及图片点击之后进入的第二个页面的标题。

如图:

这是首页,我们需要将首页的图片爬取下来

image-20240501190059451

这是第一个图片点击之后进入的页面,我们要将这个页面的标题爬取下来

image-20240501190153420

主程序.py

import scrapy
from .. import items


class MvSpider(scrapy.Spider):
    name = "mv"
    allowed_domains = ["www.ddcomic.com"]
    start_urls = ["https://www.ddcomic.com/"]

    def parse(self, response):
        #将爬取下来的页面写入test.html文件中,看看是否爬取成功
        with open("test.html", 'w', encoding='utf-8') as fd:
            fd.write(response.text)
         #通过xpath找到首页指定的位置:图片地址和下一页的链接地址
        a_list = response.xpath('//div[@class="module-items module-poster-items-base"]/a')
        
        print('大叫好我飞机撒开机', a_list.extract_first())
        print('大叫好我飞机撒开机', type(a_list))
        # 爬取下来的是seletorlist集合,我们遍历这个集合得到每一个seletor对象,在这个对象里面就封装了我们需要的数据
        for a in a_list:
            # 第一页的图片地址和第二页的url
            #selector对象是可以使用xpath的,我们通过xpath取到图片和链接地址
            href = a.xpath('./@href').extract_first()
            img = a.xpath("./div//div[@class='module-item-pic']/img/@data-original").extract_first()
            # 第二页的地址 因为得到的地址是没有前缀的,所以需要手动加入
            url = 'https://www.ddcomic.com' + href
            # 对第二页发起访问 这个就是调用第二页也就是下面这个方法,meta可以将本方法中的数据传递到下一个方法中
            yield scrapy.Request(url=url, callback=self.parse_scond, meta={'img': img})

            #这个方法用来处理第二个页面中的内容
    def parse_scond(self, response):
        #在这里我们操作的就是第二个页面了,定位到第二个页面的标题的位置,将其读取出来
        name = response.xpath('//div[@class="module-info-heading"]/h1/text()').extract_first()
        print("第二页的标题名", name)
        # 接收从第一个方法传递过来meta中的值
        img = response.meta['img']
        print('img的值是',img,'name的值是',name)
        #将数据封装成一个对象返回给管道
        movie = items.ScrapyDytt34Item(img=img, name=name)
        # 将move返回给管道
        yield movie

在settings.py中开启管道

# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   "scrapy_dytt_34.pipelines.ScrapyDytt34Pipeline": 300,
}

在pipelines.py文件中处理数据

pipelines 管道 用来处理下载的数据

class ScrapyDytt34Pipeline:
    def open_spider(self,spider):
        self.fd = open('move.json','w',encoding='utf-8')

    def process_item(self, item, spider):
        self.fd.write(str(item))
        return item

    def close_spider(self,spider):
        self.fd.close()

将豆瓣top250数据写入excel文件

创建scrapy项目

scrapy startproject douban_scrapy

进入到spiders文件夹下,创建spider文件,指定要爬取的网页的起始地址

scrapy genspider douban https://movie.douban.com/top250

创建成功之后使用pycharm打开,每一个项目应该有自己的专属虚拟环境,每一个项目都要有自己的包,不要项目里面嵌套项目。

image-20240507175920127

添加项目的虚拟环境,每一个项目都要有自己打的虚拟环境,在pycharm项目的setting中,找到Python Interpreter,选择添加interpreter

image-20240507175308238

点击Add Interpreter之后发现新的环境没有依赖项,我们将本项目需要的依赖项目导入即可

image-20240507180158588

打开终端,默认会自动激活虚拟环境,在这里安装依赖项

image-20240507180244340

在虚拟环境中安装scrapy

pip install scrapy

安装成功即可开始我们的项目了。

关闭君子协议

在settings中设置UA

USER_AGENT = "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"

运行项目查看是否能够正常运行

scrapy crawl douban

如果运行正常则开始爬取数据

在items中定义数据结构

class MoveItem(scrapy.Item):
    # define the fields for your item here like:
    rank = scrapy.Field()
    pic = scrapy.Field()
    move_name = scrapy.Field()
    describe = scrapy.Field()

在主文件中通过xpath进行爬取

    def parse(self, response):
        # 方式1
        # sc = Selector(response)
        # sc.xpath()

        # 方式2
        move_list = response.xpath("//ol[@class='grid_view']//div[@class='item']")
        for move in move_list:
            rank = move.xpath('./div[@class="pic"]/em/text()').extract_first()
            pic = move.xpath('./div[@class="pic"]/a/img/@src').extract_first()
            move_name = move.xpath('./div[@class="pic"]/a/img/@alt').extract_first()
            describe = move.xpath('./div[@class="info"]//p[@class="quote"]/span/text()').extract_first()
            # 封装成items对象,并返回给管道
            yield items.MoveItem(rank=rank, pic=pic, move_name=move_name, describe=describe)

运行命令,以csv文件的形式输出结果

scrapy crawl douban -o douban.csv

这只是爬取了一页的数据,我们要爬取所有页面的数据

在parse方法中定义下一页的url地址,将其返回,由框架自己爬取下一页

 def parse(self, response):


        # 方式2
        move_list = response.xpath("//ol[@class='grid_view']//div[@class='item']")
        for move in move_list:
            rank = move.xpath('./div[@class="pic"]/em/text()').extract_first()
            pic = move.xpath('./div[@class="pic"]/a/img/@src').extract_first()
            move_name = move.xpath('./div[@class="pic"]/a/img/@alt').extract_first()
            describe = move.xpath('./div[@class="info"]//p[@class="quote"]/span/text()').extract_first()
            # 封装成items对象,并返回给管道
            yield items.MoveItem(rank=rank, pic=pic, move_name=move_name, describe=describe)

        # 爬取每一页的数据
        # 找到下一页的链接地址
        next_url = response.xpath('//div[@class="paginator"]/a/@href')
        #         构造出Request,将这个Request返回
        for url in next_url:
            # 拼接url
            # url = "https://movie.douban.com/top250" + url.extract()
            url = response.urljoin(url.extract())
            # 将url写入Request中并返回
            print(url)
            yield scrapy.Request(url=url)

再次输入运行命令,结果中将其他页面的数据也会爬取出来。出现一个问题,因为首页的url有两个,所以会被爬取两次,出现多余数据。如果页面爬取过了调度器会管理,不会再爬取这些重复的页面。

scrapy crawl douban -o douban.csv

处理爬取多余数据的方式

方式1:修改起始url,将其定义成和其他url一样的格式(没成功)

https://movie.douban.com/top250
    修改成
https://movie.douban.com/top250?start=0&filter=
       

方式2:定义可变的起始url,通过scrapy中的方法

删除原来的 start_urls = ["https://movie.douban.com/top250?start=0&filter="
自定义start_rquest方法,返回Request               
def start_requests(self) -> Iterable[Request]:
        for i in range(10):
            url = f"https://movie.douban.com/top250?start={i*25}&filter="
            print(url)
            yield scrapy.Request(url=url)

在管道中定义写入excel的操作

下载openpyxl库

pip install openpyxl

导入库,定义方法进行写入操作

import openpyxl


# 写入excel表中
class DoubanScrapyPipeline:
    def __init__(self):
        self.wb = openpyxl.Workbook()  # 打开工作蒲
        self.ws = self.wb.active  # 激活工作表
        self.ws.append(('排名', '图片', '标题', '描述', '前言'))  # 定义表格头

    def process_item(self, item, spider):
        # 获取到对应的数据
        rank = item.get('rank', "")
        pic = item.get('pic', "")
        title = item.get('title', "")
        describe = item.get('describe', "")
        intro = item.get('intro', "")
        # 写入excel表
        self.ws.append((rank, pic, title, describe, intro))
        return item

    def close_spider(self, spider):
        # 保存数据
        self.wb.save("电影表单.xlsx")

开启管道:

ITEM_PIPELINES = {
   "douban_scrapy.pipelines.DoubanScrapyPipeline": 300,
}

运行项目

scrapy crawl douban

爬取内页

在上面的基础上增加处理内页的方法,并且修改items中的数据结构,增加内页的数据

class MoveItem(scrapy.Item):
    # define the fields for your item here like:
    rank = scrapy.Field()
    pic = scrapy.Field()
    title = scrapy.Field()
    describe = scrapy.Field()
    intro = scrapy.Field()









from typing import Iterable

import scrapy
from scrapy import Request

from douban_scrapy.items import MoveItem


class DoubanSpider(scrapy.Spider):
    name = "douban"
    allowed_domains = ["movie.douban.com"]

    def start_requests(self) -> Iterable[Request]:
        for i in range(10):
            url = f'https://movie.douban.com/top250?start={i * 25}&filter='
            yield Request(url=url)

    def parse(self, response):
        div_list = response.xpath("//ol[@class='grid_view']//div[@class='item']")
        for li in div_list:
            moveItem = MoveItem()
            rank = li.xpath("./div[@class='pic']/em/text()").get()
            pic = li.xpath("./div[@class='pic']/a/img/@src").get()
            title = li.xpath("./div[@class='info']/div[@class='hd']/a/span[1]/text()").get()
            describe = li.xpath('./div[@class="info"]//p[@class="quote"]/span/text()').extract_first()
            # 内页的地址
            inner_url = li.xpath("./div[@class='info']/div[@class='hd']/a/@href").get()
            moveItem['rank'] = rank
            moveItem['pic'] = pic
            moveItem['title'] = title
            moveItem['describe'] = describe
            # 将内页的url地址以及本页获取到的数据传递给下一个方法处理
            yield Request(url=inner_url, callback=self.second_parse, cb_kwargs={"moveItem": moveItem})

    # 这也是一个钩子函数,这里处理内页
    def second_parse(self, response, **kwargs):
        # link - report - intra
        intro = response.xpath('//div[@id="link-report-intra"]//span[@property="v:summary"]/text()').get()
        print("fdsfasfasfzahngsa=====",type(intro))
        print("fdsfasfasfzahngsa=====",intro)
        intro = intro.strip()
        moveItem = kwargs['moveItem']
        moveItem['intro'] = intro
        yield moveItem

将豆瓣top250数据写入数据库

在上面的基础上定义一个管道,将数据写入到数据库中

class DBSpiderPipeline:
    def __init__(self):
        self.conn = pymysql.connect(host='localhost', port=3306, user='root', password='root', database='spider',
                                    charset='utf8')
        # 获取游标
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        rank = item.get('rank', 0)
        pic = item.get('pic', "")
        move_name = item.get('move_name', "")
        describe = item.get('describe', "")
        sql = f'insert into tb_top_movie() values("{rank}","{pic}","{move_name}","{describe}")'
        print(sql)
        self.cursor.execute(sql)
        return item

    def close_spider(self, spider):
        self.conn.commit()
        self.cursor.close()
        self.conn.close()
批处理写入数据库

上面的代码中每次获取到数据之后立即就保存在数据库中,这样增加了IO操作,影响效率。可以使用批处理的方式,一批一批传入数据库

class DBSpiderPipeline:
    def __init__(self):
        self.conn = pymysql.connect(host='localhost', port=3306, user='root', password='root', database='spider',
                                    charset='utf8')
        # 获取游标
        self.cursor = self.conn.cursor()
        #定义一个列表,用于存储数据
        self.data = []

    def process_item(self, item, spider):
        rank = item.get('rank', 0)
        pic = item.get('pic', "")
        move_name = item.get('move_name', "")
        describe = item.get('describe', "")
        # 将数据加入到列表
        self.data.append((rank, pic, move_name, describe))
       # 当列表中的数据等于100的时候在执行插入数据库的操作
        if len(self.data) == 100:
            self._writer_database()
            #提交成功之后清空列表中的数据
            self.data.clear()
        return item

    def _writer_database(self):
        # 定义批处理的插入方式
        sql = 'insert into tb_top_movie() values(%s,%s,%s,%s)'
        self.cursor.executemany(sql, self.data)
        self.conn.commit()

    def close_spider(self, spider):
        # 爬取完成之后剩余的数据不够100条也将剩下的数据全部插入到数据库中。
        if self.data > 0:
            self._writer_database()
        self.conn.commit()
        self.cursor.close()
        self.conn.close()
IP代理

方式1:在主文件中的Request请求中有一个mate属性,这个属性的属性值是一个字典。我们可以在请求中添加代理。使用vpn代理。

   def start_requests(self) -> Iterable[Request]:
        for i in range(10):
            url = f"https://movie.douban.com/top250?start={i * 25}&filter="
            print(url)
           yield scrapy.Request(url=url, meta={'proxy': 'http://36.6.144.237:8089'})

方式2:在中间件文件中进行编写,找到middlewares.py中的class:DownloaderMiddleware -process_request(),这是一个钩子函数,可以使用该方法的request参数进行代理操作。

    def process_request(self, request: Request, spider):

        request.meta={'proxy':''}

        return None
下载中间件的使用(代理+cookie)

如果我们登录之后得到cookie,一般网站就不会进行拦截了。我们将cookie带入scrapy,在中间件中模拟用户cookie登录。

第一种方式:在请求中添加cookie

 def start_requests(self) -> Iterable[Request]:
        for i in range(10):
            url = f"https://movie.douban.com/top250?start={i * 25}&filter="
            print(url)
            yield scrapy.Request(url=url, cookies='sdfasf')

方式2:采用中间件方式,在中间件类中定义下面的方法

# 对cookie进行切分,以下是一个完整的cookie,通过;分割之后得到了xxx=xxx的形式,之后我们再通过=进行切分得到字典的形式返回。
UM_distinctid=18f0fd2c56ce0-02bba1091bf484-3e604809-1fa400-18f0fd2c56ded; _ati=4722370614660; first_visited=1; search_history_goods=; CNZZDATA1257041518=1091661087-1713957422-https%253A%252F%252Fwww.baidu.com%252F%7C1714266828; _wsy_=6c3f423e5007049ef688887dcd1ac777; usertype=1; city_temp=%E5%BC%A0%E5%AE%B6%E7%95%8C; CATEGORYPID-SITE_ID:1=30; HOMEPTYPE-SITE_ID:1=30; SERVERID=f09c0b3a9e5f2e01aa660a75953d5877|1715090761|1715090760
        
        
def  get_cookie_dict():
    cookies_str = 'UM_distinctid=18f0fd2c56ce0-02bba1091bf484-3e604809-1fa400-18f0fd2c56ded; _ati=4722370614660; first_visited=1; search_history_goods=; CNZZDATA1257041518=1091661087-1713957422-https%253A%252F%252Fwww.baidu.com%252F%7C1714266828; _wsy_=6c3f423e5007049ef688887dcd1ac777; usertype=1; city_temp=%E5%BC%A0%E5%AE%B6%E7%95%8C; '
    cookies_dict={}
    for item in cookies_str.split(';'):
        key,value = item.split('=',maxsplit=1)
        cookies_dict[key] =value
    return cookies_dict

在DownloaderMiddleware中的process_request方法中进行配置

COOKIES_DICT = get_cookie_dict()
def process_request(self, request: Request, spider):
         
        request.cookies=COOKIES_DICT
       
        return None

在settings中进行配置,中间件和管道一样都需要进行配置。

DOWNLOADER_MIDDLEWARES = {
   "douban_scrapy_spider.middlewares.DoubanScrapySpiderDownloaderMiddleware": 543,
}
值越小越先执行

完整案例

from typing import Iterable

import scrapy
from scrapy import Selector, Request

from scrapy_douban.items import MoveItem


class DoubanSpider(scrapy.Spider):
    name = "douban"
    allowed_domains = ["movie.douban.com"]
    start_urls = ["https://movie.douban.com/top250"]

    def start_requests(self) -> Iterable[Request]:
        for page in range(10):
            yield Request(url=f'https://movie.douban.com/top250?start={page * 25}&filter=')


    def parse(self, response, **kwargs):
        sel = Selector(response)
        list_item = sel.xpath("//div[@class='item']/div[@class='pic']")
        for li in list_item:
            rank = li.xpath('./em/text()').get()
            title = li.xpath("./a/img/@alt").get()
            pic = li.xpath("./a/img/@src").get()
            move = MoveItem(rank=rank, title=title, pic=pic)
            yield move

items.py

import scrapy


class MoveItem(scrapy.Item):
    rank = scrapy.Field()
    title = scrapy.Field()
    pic = scrapy.Field()

pipelines.py

在settings中开启管道之后

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter

"""
    钩子函数(方法)-->回调方法(函数)-->callback
    这些都不是我们调用的,都是scrapy框架来调用。
"""
import openpyxl


class ScrapyDoubanPipeline:
    def __init__(self):
        self.wb = openpyxl.Workbook()  # 创建工作簿,一个excel里面有很多工作表
        self.ws = self.wb.active  # 激活工作表
        self.ws.title = 'Top250'
        self.ws.append(('排名', '标题', '图片地址'))

    def process_item(self, item, spider):
        # self.ws.append((item['rank'], item['title'], item['pic']))
        rank = item.get('rank', "")
        title = item.get('title', "")
        pic = item.get('pic', "")
        self.ws.append((rank, title, pic))
        return item

    # 关闭爬虫的方法(这个固定的方法名)
    def close_spider(self, spider):
        self.wb.save('电影数据.xlsx')
yield的作用
在 Scrapy 中,yield 语句用于生成一个 Request 或 Item 对象,并将其提交给 Scrapy 引擎进行处理。这个对象可以是一个需要下载的新页面的请求(Request),或者是从当前页面中提取的数据条目(Item)。

具体来说,在爬虫代码中,yield 通常用于以下两个方面:

在解析函数中生成 Item 对象,用于将爬取到的数据保存到数据库或者文件中。

在解析函数中生成 Request 对象,用于构造下一次请求。当爬虫解析完一个页面后,需要根据页面内容构造出下一次请求并发送给服务器。这时可以使用 yield 语句生成新的 Request 对象,并将其交给Scrapy 引擎处理。
地址:https://blog.csdn.net/weixin_41657089/article/details/130852772

输出依赖清单项

查看清单

pip list 
或者
pip freeze 

输出依赖清单

pip freeze > requirements.txt

image-20240505204406317

根据清单安装依赖

pip install -r requirements.txt

crawlspider

链接提取器,可以定义规则将页面中的所有链接提取出来。

img

演示

打开scrapy shell 网址

提取链接提取器可以提取当前页面中所有符合规则的链接
from scrapy.linkextractors import LinkExtractor

#正则表达式
# \d:表示数字 +表示1到多个数字
link = LinkExtractor(allow=r'/play/4483-1-\d+\.html')
#输出
link.extract_linke(response)


#xpath方式
link1 = LinkExtractor(restrict_xpaths="//div[@class='module-play-list']/div/a/@href")
#输出
link.extract_linke(response)
crawlSpider案例

需求读书网数据入库

创建项目:scrapy startproject dushuproject
跳转到spiders路径
创建爬虫类:scrapy genspider -t crawl 项目名 爬取的域名https://www.dushu.com/book/1188.html
items
spiders
settings
pipelines
	数据保存到本地
    数据保存到mysql数据库
    

案例:爬取每一页的读书网图片和名字

image-20240501210229769

创建项目之后,在主文件中

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from .. import items

class ReadSpider(CrawlSpider):
    name = "read"
    allowed_domains = ["www.dushu.com"]
    # 这是需要写的和其他页面一样,包含第一页,不然爬取的时候不会爬取第一页
    start_urls = ["https://www.dushu.com/book/1188_1.html"]

    # /book/1188_2.html ,正则表达式,页面地址
    rules = (Rule(LinkExtractor(allow=r"/book/1188_\d+\.html"), callback="parse_item", follow=False),)

    def parse_item(self, response):
        # xpath编写地址
        img_list = response.xpath("//div[@class='book-info']//a/img")
        for img in img_list:
            name = img.xpath('./@data-original').extract_first()
            src = img.xpath('./@alt').extract_first()

            book = items.ScrapyRead35Item(name=name,src=src)
            #返回给管道
            yield book

items.py中定义要爬取的数据的结构

class ScrapyRead35Item(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    name = scrapy.Field()
    src = scrapy.Field()

在settings中开启管道之后定义管道

class ScrapyRead35Pipeline:
    def open_spider(self, spider):
        self.fd = open('book.json','w',encoding='utf-8')

    def process_item(self, item, spider):
        self.fd.write(str(item))
        return item

    def close_spider(self, spider):
        self.fd.close()
 

scrapy数据写入数据库
需要pymysql库
创建数据库数据表,数据表中的数据要和items文件中的数据保持一致

在settings中定义数据库的相关配置

DB_HOST = 'localhost'
DB_POST = 3306
DB_USER = 'root'
DB_PASSWORD = 'root'
# 数据库名字
DB_NAME = 'spider01'
# 字符编码
DB_CHARSET = 'utf8'

在pipelines文件中下载数据执行数据库操作

在管道文件中定义我们自己的管道

class MysqlPipeline:
    def open_spider(self, spider):

    def process_item(self, item, spider):
        return item

    def close_spider(self, spider):

在settings中将自己的管道加入的配置中。

ITEM_PIPELINES = {
   "scrapy_crawlspider_dushu36.pipelines.ScrapyCrawlspiderDushu36Pipeline": 300,
   "scrapy_crawlspider_dushu36.pipelines.MysqlPipeline": 301,
   
}

在自己管道中定义执行数据库相关操作

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import pymysql
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter


class ScrapyCrawlspiderDushu36Pipeline:
    def open_spider(self, spider):
        self.fd = open("read.json", 'w', encoding='utf-8')

    def process_item(self, item, spider):
        self.fd.write(str(item))
        return item

    def close_spider(self, spider):
        self.fd.close()


# 加载settings中的文件
from scrapy.utils.project import get_project_settings

-----------------------这里是自定义的管道用于操作数据库-----------------------------------
class MysqlPipeline:
    def open_spider(self, spider):
        settings = get_project_settings()
        # 从settings中取到相关配置赋值给我们的属性
        self.host = settings['DB_HOST']
        self.port = settings['DB_PORT']
        self.user = settings['DB_USER']
        self.password = settings['DB_PASSWORD']
        self.name = settings['DB_NAME']
        self.charset = settings['DB_CHARSET']

        self.connect()

    def connect(self):
        self.conn = pymysql.connect(host=self.host, port=self.port, user=self.user,
                                    password=self.password, db=self.name, charset=self.charset)

        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        sql = 'insert into book(name,src) values("{}","{}")'.format(item['name'], item['src'])
        # 执行sql
        self.cursor.execute(sql)
        # 提交
        self.conn.commit()
        return item
	#	关闭数据库
    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()

执行数据就能爬取13页的数据并且将其添加到数据库中

在主配置文件中,将follow设置成为True,可以爬取所分页码中所有的数据。超过13页

 rules = (Rule(LinkExtractor(allow=r"/book/1188_\d+\.html"), callback="parse_item", follow=True),)
    这里的follow表示是否跟进,就是按照提前连续规则进行提取

image-20240502005616368

将数据输出到文本文件中
import scrapy
from scrapy import Selector

from scrapy_douban.items import MoveItem


class DoubanSpider(scrapy.Spider):
    name = "douban"
    allowed_domains = ["movie.douban.com"]
    start_urls = ["https://movie.douban.com/top250"]

    def parse(self, response):
        sel = Selector(response)
        list_item = sel.xpath("//div[@class='item']/div[@class='pic']")
        for li in list_item:
            rank = li.xpath('./em/text()')
            title = li.xpath("./a/img/@alt").get()
            pic = li.xpath("./a/img/@src").get()
            #爬取到数据后封装成item对象,直接返回
            move = MoveItem(rank=rank, title=title, pic=pic)
            yield move

在运行的时候输出

scrapy crawl douban -o douban.csv
支持的文件有:('json', 'jsonlines', 'jsonl', 'jl', 'csv', 'xml', 'marshal', 'pickle')

scrapy的日志信息和日志等级

img

在settings中进行设置

# 指定日志级别:(我们一般不改变),改变日志等级如果错误无法看到
# LOG_LEVEL = 'WARNING'

#我们一般采用日志文件的方式,将日志输出到文件中
LOG_FILE = 'logdemo.log'

运行不要输出日志

scrapy crawl douban --nolog

scrapy的post请求

img

import scrapy
import json


class TestpostSpider(scrapy.Spider):
    name = "testpost"
    allowed_domains = ["fanyi.baidu.com"]

    # post请求没有参数会报错
    # post请求和start_urls 和parse方法没有任何关系
    # start_urls = ["https://fanyi.baidu.com/sug"]

    # def parse(self, response):
    #     pass
	
    #post请求必须
    def start_requests(self):
        url = "https://fanyi.baidu.com/sug"
        data = {
            'kw': 'final'
        }
        # 发送请求
        yield scrapy.FormRequest(url=url, formdata=data, callback=self.parse_second)

    def parse_second(self, response):
        # 这里的response就是执行了上面的url的返回结果
        # 方式1
        # obj = response.json()
        # 方式2 使用json方式
        content = response.text
        obj = json.loads(content)
        print(obj)
scrapy中的settings项
# 设置代理UA
USER_AGENT = r"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)"
#设置并发数量(默认16)
CONCURRENT_REQUESTS = 32
#下载延迟
DOWNLOAD_DELAY = 3
#随机延迟
RANDOMIZE——DOWNLOAD_DELAY =True

json

image-20240508151030811

python的JSON用法——dumps的各种参数用法(详细)_json.dump()方法-CSDN博客

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
感谢您的提问。我理解您想了解如何用 Python 爬虫爬取雪球网的金融数据。下面是一个简单的爬虫项目,可以帮助您实现这个目标: 1. 确定爬取的目标网页 我们需要确定要爬取的目标网页。在本例中,我们将使用雪球网的金融数据页面:https://xueqiu.com/hq。 2. 使用 requests 库发送 HTTP 请求 我们可以使用 Pythonrequests 库来发送 HTTP 请求并从目标网页获取数据。以下是如何发送 GET 请求的示例代码: ```python import requests url = 'https://xueqiu.com/hq' response = requests.get(url) print(response.text) ``` 3. 解析 HTML 页面 我们可以使用 Python 的 Beautiful Soup 库来解析 HTML 页面。以下是如何使用 Beautiful Soup 库解析响应的 HTML 页面的示例代码: ```python from bs4 import BeautifulSoup soup = BeautifulSoup(response.text, 'html.parser') # 打印解析后的 HTML 页面 print(soup.prettify()) ``` 4. 提取数据 一旦我们成功解析了 HTML 页面,就可以使用 Beautiful Soup 库中的一些方法来提取我们需要的数据。以下是如何从 HTML 页面中提取股票代码和股票名称的示例代码: ```python # 找到包含股票代码和名称的表格 table = soup.find('table', {'class': 'table'}) # 找到所有的行 rows = table.find_all('tr') # 遍历每一行,并提取股票代码和名称 for row in rows: cols = row.find_all('td') if len(cols) > 0: code = cols[0].text.strip() name = cols[1].text.strip() print(code, name) ``` 5. 存储数据 最后,我们可以将提取的数据存储到文件中。以下是如何将股票代码和名称存储到 CSV 文件中的示例代码: ```python import csv with open('stocks.csv', 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(['Code', 'Name']) for row in rows: cols = row.find_all('td') if len(cols) > 0: code = cols[0].text.strip() name = cols[1].text.strip() writer.writerow([code, name]) ``` 希望这个爬虫项目可以帮助您了解如何用 Python 爬虫爬取雪球网的金融数据。如果您还有其他问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值