【学习笔记】Python爬虫

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

代码语言:javascript
复制
<!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>
        &lt;tr&gt;
            &lt;td&gt;小明&lt;/td&gt;
            &lt;td&gt;18&lt;/td&gt;
            &lt;td&gt;男&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/table&gt;

<!-- 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. 解析 基本使用
代码语言:javascript
复制
# 以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)

类型与方法

代码语言:javascript
复制
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())

下载

代码语言:javascript
复制
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')

请求对象的定制

代码语言:javascript
复制
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方法

代码语言:javascript
复制
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方法

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

代码语言:javascript
复制
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请求 - 百度翻译

代码语言:javascript
复制
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 - 百度翻译之详细翻译

代码语言:javascript
复制
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,再前端进行数据渲染

代码语言:javascript
复制
# 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请求

代码语言:javascript
复制
# 不同的页比较一下 发现不同页数主要是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捕获异常

代码语言:javascript
复制
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 判断防盗链-当前连接是不是由上一个路径进来 --- 一般为图片防盗链

代码语言:javascript
复制
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\代理
定制更高级的请求头


基本使用 - 百度
代码语言:javascript
复制
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
代码语言:javascript
复制
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)

代理池
代码语言:javascript
复制
# 先找到一堆有用的代理,然后随机出来
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形式

基本操作
代码语言:javascript
复制
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 - 获取百度网站的百度一下
代码语言:javascript
复制
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 - 站长素材

下载前十页的图片

代码语言:javascript
复制
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]
# 有些网站用'&#39; 可以使用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文件

代码语言:javascript
复制
{ "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
}
}
}
代码语言:javascript
复制
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)

案例 - 解析淘票票
代码语言:javascript
复制
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的解析器
本地和响应对象都可以解析

代码语言:javascript
复制
优缺点:
优点: 接口人性化, 使用方便(针对有基础的人)
缺点: 效率没有lxml高

基本使用
代码语言:javascript
复制
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一个错误,取的名字不要与导入的包一致

代码语言:javascript
复制
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

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

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

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


为什么学它?

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

安装selenium

  1. 下载一个谷歌浏览器驱动 - win32就行 - 解压后放在python文件目录下就行了
  2. 谷歌驱动和谷歌浏览器之间的映射表
  3. 查看谷歌的版本 - 帮助-关于
  4. pip install selenium
    基本使用
代码语言:javascript
复制
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)

元素定位

模拟鼠标和键盘来操作

代码语言:javascript
复制
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)

元素信息

代码语言:javascript
复制
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)

交互

代码语言:javascript
复制
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文件导过来

代码语言:javascript
复制
无界面浏览器,运行快
因为它基本凉了,我就没有自己动手再现视频了
代码语言:javascript
复制
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

代码语言:javascript
复制
chrome的一种模式
要求:chrome >= 59/60
python 3.6
selenium 3.4.*
ChromeDriver 2.31
代码语言:javascript
复制
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 (可以加国内源)

基本使用

代码语言:javascript
复制
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里?可加可不加
代码语言:javascript
复制
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. 不需要请求对象定制
代码语言:javascript
复制
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)

代理

我这里遇到中文乱码(悲

代码语言:javascript
复制
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]验证码

难点

隐藏域类似__xx

验证码

代码语言:javascript
复制
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

代码语言:javascript
复制
.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改掉

代码语言:javascript
复制
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 爬虫文件

代码语言:javascript
复制
init
自定义 ******

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

代码语言:javascript
复制
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(&#39;==========================&#39;)
    # print(content)
    # 直接解析
    aaa = response.xpath(&#39;/html/body/div[3]/div[1]/div[1]/div/div[3]/div[1]/div[4]/a&#39;)[0]
    print(&#39;=============================&#39;)
    # 提取seletor对象的(data)属性值
    print(aaa.extract())
    # .extract_first() 是提取seletor列表的第一个数据</code></pre></div></div><h4 id="17sho" name="%E6%B1%BD%E8%BD%A6%E4%B9%8B%E5%AE%B6---%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86">汽车之家 - 工作原理</h4><p>后缀是html时,不能加/

因为网站改了+scrapy的版本不同了,也就没有本地再现

代码语言:javascript
复制
name_list = response.xpath(***/text())
price_list = ....
for i in range(len..):
print(..,..)

scrapy的架构组成

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

工作原理

代码语言:javascript
复制
spiders --url-> 引擎 --url-> 调度器 --请求-> 下载器
--从互联网下载数据,数据(response)-> 引擎
--数据-> spiders(通过xpath解析数据) --解析结果-> 引擎

  1. --url-> 引擎(循环)

  2. --数据-> 管道(存到文件、数据库)

scrapy shell

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

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

案例 - 当当网

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

items.py

代码语言:javascript
复制
src = scrapy.Field()
name = scrapy.Field()
price = scrapy.Field()

dang.py

代码语言:javascript
复制
# 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(&#39;//ul[@class=&#34;bigimg&#34;]/li&#39;)
 for li in li_list:
     title = li.xpath(&#39;./a/@title&#39;).extract_first()
     # 图片懒加载找origanal
     # 检查数据后发现,第一张没有origanal
     src = li.xpath(&#39;./a/img/@data-original&#39;).extract_first()
     if not src:
         src = li.xpath(&#39;./a/img/@src&#39;).extract_first()
     price = li.xpath(&#39;./p[@class=&#34;price&#34;]/span[1]/text()&#39;).extract_first()
     print(title,src,price)</code></pre></div></div><h5 id="avi34" name="%E7%AE%A1%E9%81%93%E5%B0%81%E8%A3%85">管道封装</h5><p><code>如果要使用管道,需要在settings中开启管道</code>

settings.py

代码语言:javascript
复制
# ITEM_PIPELINES 的注释解开

管道可以有很多个 优先级为1->1000,值越小,优先级越高

pipelinse.py

代码语言:javascript
复制
class DangdangPipeline:
# 在爬虫文件执行前执行
def open_spider(self, spider):
    # print(&#39;+++++++++++++++++++++++++&#39;)
    self.fp = open(&#39;book.json&#39;,&#39;w&#39;,encoding=&#39;utf-8&#39;)

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

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

    return item

# 在爬虫文件执行后执行
def close_spider(self, spider):
    # print(&#39;------------------------&#39;)
    self.fp.close()</code></pre></div></div><p><code>dang.py - 添加</code></p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">from ..items import DangdangItem

dangdang.items 会报错来着

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

多条管道下载

pipelines.py - 添加

代码语言:javascript
复制
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

代码语言:javascript
复制
ITEM_PIPELINES = {
'dangdang.pipelines.DangdangPipeline': 300,
'dangdang.pipelines.DangDangPipeline_Pic':301,
}
多页下载

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

dang.py + 在parse函数中

代码语言:javascript
复制
    # 多页下载,必须要调整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 &lt; 10:
        self.page += 1
        url = &#39;http://category.dangdang.com/pg{}-cp01.01.02.00.00.00.html&#39;.format(self.page)

        # 调用parse
        # scrapy.Request是scrapy的get请求
        # url请求地址
        # callback要执行的函数,不加()
        yield scrapy.Request(url=url,callback=self.parse)</code></pre></div></div><h4 id="4i4go" name="%E6%A1%88%E4%BE%8B---%E7%94%B5%E5%BD%B1%E5%A4%A9%E5%A0%82%E5%A4%9A%E9%A1%B5%E4%B8%8B%E8%BD%BD">案例 - 电影天堂多页下载</h4><p>先看有没有反爬(直接print======</p><p><code>xpath拿不到数据,检查xpath语法正确性。 span不能识别!!</code></p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0"># 对第二页访问

meta字典型, 为了让name和src在一起,传进去

    ...
    yield scrapy.Request(url=url,callback=parse_second,meta={&#39;name&#39;:name})
def parse_second(self,response):
    # //div[@id=&#34;Zoom&#34;]/span/img/@src
    # 拿不到数据,检查xpath语法正确性。 span不能识别!!
    src = response.xpath(&#39;//div[@id=&#34;Zoom&#34;]//img/@src&#39;).extract_first()
    name = response.meta[&#39;name&#39;]
    movie = MovieHeavenItem(src=src,name=name)

    yield movie</code></pre></div></div><h4 id="8edb1" name="CrawlSpider---%E8%BF%9E%E6%8E%A5%E6%8F%90%E5%8F%96%E5%99%A8">CrawlSpider - 连接提取器</h4><ul class="ul-level-0"><li>Mysql</li></ul><ol class="ol-level-0"><li>下载</li><li>安装</li></ol><p>pymysql</p><p>继承自scrapy.Spider</p><p>比如网站页码,可以知道链接,链接的解析规则一致</p><p>使用scrapy shell</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">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个人感觉更偏向于企业级开发,不是特别感冒,就咕咕咕了... ^ ^