Python实用工具 | 自主研发-购书比价工具| 01


要爬取的网站

www.dangdang.com
www.jd.com
www.yhd.com
www.taobao.com

课程概要及环境搭建

需求:输入图书的ISBN编码,可以获取多家网上书城的价格,并按照价格排序输出结果。

json知识点学习

JSON:

  • 一种轻量级的数据交换格式;通用,跨平台

  • “key -value”的集合;值的有序列表

这是概念性的东西,这里只是简要的提一下,后面写代码的时候会进行详细的概述。

  • 类似Python中得dict

上面这张表需要好好掌握住。

Python和Json字符串的相互转换是要学会的。

然后最重要的一点就是从文件中读取Json字符串,将其转换为Python对象,这个在后面爬虫中也是需要被用到的。

下面是book.json,这是事先准备好json文件数据。

{
    "name": "Python书籍",
    "origin_price": 66,
    "pub_date": "2018-4-14 17:00:00",
    "store": ["京东", "淘宝"],
    "author": ["张三", "李四", "Jhone"],
    "is_valid": true,
    "is_sale": false,
    "meta": {
        "isbn": "abc-123",
        "pages": 300
    },
    "desc": null
}

Json中的key必须是双引号的,不像Python的key,可以单引号也可以双引号。

Json中的key也是唯一的,不能有同名的key。

Python与Json转换API:

  • Python3的标准库 json

  • dumps是将dict转化成str格式,loads是将str转化成dict格式。

  • dump和load也是类似的功能,只是与文件操作结合起来了。

use_json.py

import json


def python_to_json():
    """
    将Python对象转换成json字符串 json.dumps()
    """
    d = {
        'name': 'python书籍',
        'price': 62.3,
        'is_valid': True
    }
    res = json.dumps(d,indent=4) # 加上缩进
    print("Python转换为Json:", res)
    print("类型:", type(res))


def json_to_python():
    """
    将json字符串转换为Python对象 json.loads()
    """
    data = '''
        {
            "name": "Python书籍",
            "origin_price": 66,
            "pub_date": "2018-4-14 17:00:00",
            "store": ["京东", "淘宝"],
            "author": ["张三", "李四", "Jhone"],
            "is_valid": true,
            "is_sale": false,
            "meta": {
                "isbn": "abc-123",
                "pages": 300
            },
            "desc": null
        }
    '''
    res = json.loads(data)
    print("Json转换为Python:", res)
    print("类型:", type(res))

def json_to_python_from_file():
    """
    从文件读取内容,并转换成Python对象
    """
    with open("./static/book.json","r",encoding="utf8") as f:
        s = f.read() # 读取文件数据
        print("Json文件内容:",s)
        res = json.loads(s)
        print("读取Json文件内容,转换为Python对象:",res)
        print("类型:", type(res))
        


if __name__ == "__main__":
    python_to_json()
    print("="*20)
    json_to_python()
    print("="*20)
    json_to_python_from_file()

执行结果

Python转换为Json: {
    "name": "python\u4e66\u7c4d",
    "price": 62.3,
    "is_valid": true
}
类型: <class 'str'>
====================
Json转换为Python: {'name': 'Python书籍', 'origin_price': 66, 'pub_date': '2018-4-14 17:00:00', 'store': ['京东', '淘宝'], 'author': ['张三', '李四', 'Jhone'], 
'is_valid': True, 'is_sale': False, 'meta': {'isbn': 'abc-123', 'pages': 300}, 'desc': None}
类型: <class 'dict'>
====================
Json文件内容: {
    "name": "Python书籍",
    "origin_price": 66,
    "pub_date": "2018-4-14 17:00:00",
    "store": ["京东", "淘宝"],
    "author": ["张三", "李四", "Jhone"],
    "is_valid": true,
    "is_sale": false,
    "meta": {
        "isbn": "abc-123",
        "pages": 300
    },
    "desc": null
}
读取Json文件内容,转换为Python对象: {'name': 'Python书籍', 'origin_price': 66, 'pub_date': '2018-4-14 17:00:00', 'store': ['京东', '淘宝'], 'author': ['张三', 
'李四', 'Jhone'], 'is_valid': True, 'is_sale': False, 'meta': {'isbn': 'abc-123', 'pages': 300}, 'desc': None}
类型: <class 'dict'>

xpath及html基础知识

xPath:一种HTML和XML的查询语言,它能在XML和HTML的树状结构中寻找节点。

上图是HTML的页面结果。

如上图就是HTML的一个树形结构。

xpath实战

学习的xPath内容重点分为两块:

  • 获取文本 //标签1[@属性1=“属性值1”]/标签2[@属性2=“属性值2”]/.../text()
  • 获取属性值 //标签1[@属性1=“属性值1”]/标签2[@属性2=“属性值2”]/.../@属性n

什么场景要获取属性值呢?就是获取一个超链接的地址,比如有一个链接,是跳转百度的,那么一定是一个a标签,其中的href这个属性指向的是网站的地址。

xPath中双斜杠和单斜杠的差别:

  • 如果是单斜杠开头,就是从文档的根路径开始匹配
  • 如果是双斜杠开头,就是从任意的位置匹配

下面就使用xPath匹配下面的HTML文档。

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>网页测试</title>
	<link rel="stylesheet" href="">
</head>
<body>
	<h3>标题</h3>
	<ul>
		<li>内容1</li>
		<li>内容2</li>
		<li class="important">内容3important</li>
		<li>内容4</li>
		<li>内容5</li>
	</ul>
	<div>
		内容未知
	</div>
	<p>
		段落内容 from p
	</p>
	<div id="container">
		段落文字
		<a href="http://www.baidu.com" title="超链接">跳转到百度首页</a>
		<p class="content">
			区块内容1
		</p>
		<p class="content">
			区块内容2
		</p>
		<p class="content">
			区块内容3
		</p>
		<p class="content">
			区块内容4
		</p>
		<p class="content-block">
			区块内容5   from block
		</p>
		<p class="block-content">
			区块内容6  末尾内容
		</p>
	</div>
	<p>
		最后一段文字
	</p>
</body>
</html>

use_xpath_demo.py

from lxml import html


def parse():
    """
    将html文件的内容,使用xpath进行提取
    """
    with open("static\index.html", "r", encoding="utf8") as f:
        s = f.read()  # 获取到html字符串

        selector = html.fromstring(s)  # 解析html文档
        print(type(selector))  # <class 'lxml.html.HtmlElement'>
        # 解析h3标题
        h3 = selector.xpath('/html/body/h3/text()')  # 注意 是 / 开头
        print("h3:", h3)

        # 解析ul下面的内容 
        ul = selector.xpath("/html/body/ul/li") # 得到的是一个 <class 'lxml.html.HtmlElement'> 的list
        print("ul长度:",len(ul)) # 5
        for li in ul: # 循环输出其中的内容
            print(li.xpath('text()'))

        # 解析ul指定的元素值(在元素列表中选择指定的元素)
        """
        // 开头就是从根开始找
        这里由于只有一个ul 所以找到的结果是唯一的
        """
        ul2 = selector.xpath('//ul/li[@class="important"]/text()')
        print("ul2:",ul2) 

        # 解析a标签的内容
        a = selector.xpath('//div[@id="container"]/a')
        print("a标签的内容:",a[0].xpath('text()'))
        print("a标签的网址:",a[0].xpath('@href'))
        

if __name__ == "__main__":
    parse()

执行结果

<class 'lxml.html.HtmlElement'>
h3: ['标题']
ul长度: 5
['内容1']
['内容2']
['内容3important']
['内容4']
['内容5']
ul2: ['内容3important']
a标签的内容: ['跳转到百度首页']
a标签的网址: ['http://www.baidu.com']

提示:在chrome的检查中可以复制xpath的路径,但是仅供参考..

Requests基础知识

Request库:

  • 安装 pip install requests

  • 请求和响应 Request & Response

  • POST/GET请求

什么情况下用GET?什么情况下用POST?

一般获取数据,也就是从数据库把信息拉出来的时候,就是使用GET请求,直接通过浏览器就可以访问。

如果要改变数据库的东西,新增、删除、修改,就是要用POST。

Resquets的使用:

  • res = requests.get(url,params={}) # get请求数据

  • res = requests.post(url,params={}) # post请求数据

  • res.text # 获取html文档文本

  • res.json() # 将json响应数据转换为dict

  • res.status_code # HTTP状态码

  • res.encoding # 查看文件的编码

优雅的使用字符串

优雅的使用字符串:

  • 使用%格式化字符串
  • 使用.format进行高级操作

user_str.py

def format_str():
    """
    格式化字符串
    """
    name = "张三"
    print("欢迎您,%s" % name)
    print("您的姓名:%(name)s" % {'name': name})

    # 整型 浮点型
    num = 12.33
    print("您输入的数字是:%.1f" % num)  # 12.3
    num2 = 54
    print("您的编号是:%04d" % num2)  # 0054

    """
    使用 format() 进行格式化
    """
    # 使用位置
    print('欢迎您, {0}, {1},---{0}说'.format('张三', '好久不见'))
    # 使用名称
    d = {
        'username': '李四',
        'num': 45
    }
    print('您好,{username}, 您的编号是{num}'.format(**d))  # 字典解包
    print('您好,{username}, 您的编号是{num}'.format(username="李四", num=45))

    # 格式化元组 第一个表示位置 []表示取下标元素
    point = ((1, 2), (3, 4))
    print("坐标位置:{0[0]}:{0[1]}".format(point))

    # 格式化类
    one = User("王五",25)
    print(one.show())

class User:
    def __init__(self, username, age) -> None:
        self.username = username
        self.age = age

    def show(self):
        """给类进行格式化"""
        return "用户名:{self.username},年龄:{self.age}".format(self=self)

if __name__ == "__main__":
    format_str()

爬取当当网的数据

import requests
from lxml import html


def spider(sn, book_list=[]):
    """
    爬取当当网的数据
    params
    sn:图书的ibsn
    book_list:图书列表 
    """
    url = "http://search.dangdang.com/?key={sn}&act=input".format(
        sn=sn)  # format的用法
    html_data = requests.get(url)
    html_data.encoding = "GB2312"
    html_data = html_data.text

    # xpath对象
    selector = html.fromstring(html_data)

    # 找到书本列表(这个就要自己观察网页的结构了!)
    # 一般列表是最好爬取的
    ul_list = selector.xpath('//div[@id="search_nature_rg"]/ul/li')
    print(len(ul_list))  # 打印长度验证是否取到数据

    for li in ul_list:
        # 标题
        title = li.xpath('a/@title')
        print("书名:", title[0])
        # 购买链接
        link = li.xpath('a/@href')
        print("购买链接:", link[0])
        # 价格
        price = li.xpath('p/span[@class="search_now_price"]/text()')
        print('价格:', price[0].replace('¥',''))
        # 商家
        store = li.xpath('p[@class="search_shangjia"]/a/text()')
        store = '当当自营' if len(store) == 0 else store[0]  # 这个是要自己推断出来的!
        print('商家:', store)


if __name__ == "__main__":
    sn = '9787115428028'
    spider(sn)

爬取京东网的数据

原来京东搜索网址是这个

https://search.jd.com/Search?keyword=9787115428028&enc=utf-8&wq=9787115428028&pvid=31aef6ade5f040eb8962da95e044739f

可以适当删除,下面也不影响搜索结果

https://search.jd.com/Search?keyword=9787115428028

对于京东要登录的这波操作...

关于爬取京东的数据要先登录... 这真的是一个反爬机制阿...

import requests


headers = {
    "cookie":"...",
    "user-agent": "..."
}
html_data = requests.get(url, headers=headers)
import requests
from lxml import html


def spider(sn,book_list=[]):
    """
    爬取京东的图书数据
    params
    sn: 图书的isbn号
    """
    url = "https://search.jd.com/Search?keyword={sn}".format(sn=sn)
    # 获取HTML文档
    headers = {
        "cookie":"...",
        "user-agent": "..."
    }

    html_data = requests.get(url, headers=headers)
    html_data.encoding = "utf-8"
    html_data = html_data.text

    # 获取xpath对象
    selector = html.fromstring(html_data)
    # 找到列表的集合
    ul = selector.xpath('//div[@id="J_goodsList"]/ul/li')
    print("列表长度:", len(ul))
    # 解析对应的内容
    for li in ul:
        # 标题
        title = li.xpath('div/div[@class="p-name"]/a/em/text()')
        print("书名:", title[0])
        # 购买链接
        link = li.xpath('div/div[@class="p-name"]/a/@href')
        print("购买链接:", link[0])
        # 价格
        price = li.xpath('div/div[@class="p-price"]/strong/i/text()')
        print('价格:', price[0])
        # 商家
        store = li.xpath('div/div[@class="p-shopnum"]/a/@title')
        print('商家:', store[0])



if __name__ == "__main__":
    sn = '9787115428028'
    spider(sn)

爬取1号店的数据

(1号店已经没有了...)

爬取淘宝网的数据

淘宝的数据爬取和其他都不一样,因为它使用json的方式返回的。

淘宝的网站已经更新了,现在是通过js代码来更新商品的...

可以通过爱淘宝来进行搜索..

爬取淘宝网的难度是最大的...

实现购书比价工具

from spider_dangdang import spider as dangdang
from spider_jd import spider as jd


def main(sn):
    """
    图书比较工具整合
    """
    book_list = []

    print("====开始爬取 当当网 数据====")
    dangdang(sn, book_list)
    print("==== 当当网 数据爬取完成====")
    print("====开始爬取 京东 数据====")
    jd(sn, book_list)
    print("==== 京东 数据爬取完成====")

    # 打印所有数据列表
    # for book in book_list:
    #     print(book)

    print("===开始排序===")

    # 按照价格升序排序
    book_list = sorted(book_list, key=lambda x: float(
        x["price"]), reverse=True)

    for book in book_list:
        print(book)


if __name__ == "__main__":
    sn = input("请输入ISBN号:").strip()
    main(sn)

小结

该工具的需求是什么?

**需求:输入图书的ISBN编码,可以获取多家网上书城的价格,并按照价格排序输出结果。**

什么是Json?

*   一种轻量级的数据交换格式;通用,跨平台
*   “key -value”的集合;值的有序列表
这是概念性的东西,这里只是简要的提一下,后面写代码的时候会进行详细的概述。
*   类似Python中得dict

Json中的key必须是双引号的,不像Python的key,可以单引号也可以双引号。
Json中的key也是唯一的,不能有同名的key。 

Python-Json类型转换

Python	       Json
dict           object (重点理解!)
list,tuple     array
str            string
int,float      number
True           true
False          false
None           null

Python与Json转换API

dumps是将dict转化成str格式,loads是将str转化成dict格式。
dump和load也是类似的功能,只是与文件操作结合起来了。

Python3的标准库 json

Python如何读取和写入文件?

with open("文件路径","r/w/a",encoding="gbk/utf8") as f:
	f.read()/f.write()

什么是xPath?

xPath:一种HTML和XML的查询语言,它能在XML和HTML的树状结构中寻找节点。

xPath的使用

学习的xPath内容重点分为两块:
*   获取文本 //标签1[@属性1=“属性值1”]/标签2[@属性2=“属性值2”]/.../text()
*   获取属性值  //标签1[@属性1=“属性值1”]/标签2[@属性2=“属性值2”]/.../@属性n

什么场景要获取属性值呢?就是获取一个超链接的地址,比如有一个链接,是跳转百度的,那么一定是一个a标签,其中的href这个属性指向的是网站的地址。

xPath中双斜杠和单斜杠的差别:
*   如果是单斜杠开头,就是从文档的根路径开始匹配
*   如果是双斜杠开头,就是从任意的位置匹配

# lxmlc
from lxml import html
selector = html.fromstring(html/xml字符串)

GET & POST

GET请求
	可以用浏览器直接访问
	请求可以携带参数,但是长度有限制
	请求参数直接放在URL后面
POST请求
	 不能使用浏览器直接访问
	 对请求参数的长度没有限制
	 可以用来上传文件等需求
	 
	 


什么情况下用GET?什么情况下用POST?

一般获取数据,也就是从数据库把信息拉出来的时候,就是使用GET请求,直接通过浏览器就可以访问。
如果要改变数据库的东西,新增、删除、修改,就是要用POST。

Requests的使用

res = requests.get(url,params={}) # get请求数据
res = requests.post(url,params={}) # post请求数据
res.text # 获取html文档文本
res.json() # 将json响应数据转换为dict
res.status_code # HTTP状态码
res.encoding # 查看文件的编码

优雅的使用字符串

优雅的使用字符串:
*   使用%格式化字符串
*   使用.format进行高级操作

# %
# 字符串
"欢迎您,%s" % name
"您的姓名:%(name)s" % {'name': name}

# 浮点数 整型
"您输入的数字是:%.1f" % 12.33
"您的编号是:%04d" % 54

# format()
# 使用位置
'欢迎您, {0}, {1},---{0}说'.format('张三', '好久不见')

# 使用名称
'您好,{username}, 您的编号是{num}'.format(**d)
'您好,{username}, 您的编号是{num}'.format(username="李四", num=45)

# 格式化元组 第一个表示位置 []表示取下标元素
point = ((1, 2), (3, 4))
"坐标位置:{0[0]}:{0[1]}".format(point)

# 格式化类
class User:
    def __init__(self, username, age) -> None:
        self.username = username
        self.age = age

    def show(self):
        """给类进行格式化"""
        return "用户名:{self.username},年龄:

如何理解字典解包?

简单来说,就是把字典的内容变成方法参数中
key1=value1,key2=value2,.... 
这样的形式

    print('您好,{username}, 您的编号是{num}'.format(**d)) # 字典解包
    print('您好,{username}, 您的编号是{num}'.format(username="李四",num=45)) 

爬虫推断!

# 大部分结构是有规律的 但是有特殊的要特殊判断 这个就要靠观察了!
store = li.xpath('p[@class="search_shangjia"]/a/text()')
store = '当当自营' if len(store) == 0 else store[0] # 这个是要自己推断出来的!

一般列表的数据都是 ul 

判断是不是要爬取的对象,查看网页的Elements,要确定的话还可以再查看网页的源代码

网址删除

原来京东的网址是这个
https://search.jd.com/Search?keyword=9787115428028&enc=utf-
8&wq=9787115428028&pvid=31aef6ade5f040eb8962da95e044739f

可以适当删除,下面也不影响搜索结果
https://search.jd.com/Search?keyword=9787115428028

关于网页的编码

在网页的源代码中有 charser="..."
然后可以设置 
    html_data = requests.get(url)
    html_data.encoding = "GB2312" # 或者 utf-8
    html_data = html_data.text

对于京东要登录的这波操作...

关于爬取京东的数据要先登录... 这真的是一个反爬机制阿...

import requests


headers = {
    "cookie":"...",
    "user-agent": "..."
}
html_data = requests.get(url, headers=headers)

posted @ 2021-08-07 01:46  RowryCho  阅读(110)  评论(0编辑  收藏  举报