Ace803 发表于 2022-4-23 21:45

新手--某美女图片爬取

代码写的很糙,希望各位大佬莫笑,自学了一段时间,还在学习的过程中
昨天学了bs4,就想哪来练练手,于是有了今天的这段代码,希望大佬指正一下,代码不通顺需要提高的地方,并注释一下,将不胜感激!!!
代码如下:


```
# coding:utf-8 学好python,天天向上

import requests
from bs4 import BeautifulSoup
import time


ms=int(input('请输入爬取内容:1、性感美女 2、清纯可爱 3、性感御姐 4、制服诱惑'))
page=1
url1="https://dimgw.us/xinggan/page/"+str(page) # 性感美女
url2="https://dimgw.us/qc/page/"+str(page)      # 清纯可爱
url3="https://dimgw.us/yj/page/"+str(page)      # 性感御姐
url4="https://dimgw.us/zf/page/"+str(page)      # 制服诱惑

if ms==1:
    url=url1
elif ms==2:
    url=url2
elif ms==3:
    url=url3
elif ms==4:
    url=url4
else:
    print('输入有误,请重新输入')
headers={
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36",
    "Referer":"https://dimgw.us/zf"
      }
domain_resp=requests.get(url,headers=headers)
domain_page=BeautifulSoup(domain_resp.text,'html.parser')
# print(domain_page)
a_tag=domain_page.find_all('h2',class_="entry-title")   # 拿到想要的标签内容
# print(a_tag)
while True:
    for child_href in a_tag:
      b_tag=child_href.find_all('a')
      # print(b_tag)
      c_tag=str(b_tag) # 对内容进行切割,获取网址
      # href=c_tag.get('href')
      # print(c_tag)
      child_resp=requests.get(c_tag,headers=headers)
      child_page=BeautifulSoup(child_resp.text,'html.parser')
      # print(child_page)
      # 未完成,子页面内图片跳转到另外网站
      div=child_page.find('div',class_="entry-content u-text-format u-clearfix").find_all('img')# 获取子页面内img标签内容
      # print(div)
      for it in div:
            src=it.get('src')   # 拿到页面内的图片下载地址
            img_name= src.split('/')[-1]
            # print(src)
            img=requests.get(src,headers=headers)
            img.content
            with open(r'练习小说/'+img_name,'wb') as f: # 报错:OSError: Invalid argument:
                f.write(img.content)
            print('完成',img_name)
            time.sleep(1)
            f.close()
    page+=1
print('下载完成')
```

MyModHeaven 发表于 2022-4-24 10:23

- 我也在学爬虫,来讨论一下

# 1


```py
ms=int(input('请输入爬取内容:1、性感美女 2、清纯可爱 3、性感御姐 4、制服诱惑'))
page=1
url1="https://dimgw.us/xinggan/page/"+str(page) # 性感美女
url2="https://dimgw.us/qc/page/"+str(page)      # 清纯可爱
url3="https://dimgw.us/yj/page/"+str(page)      # 性感御姐
url4="https://dimgw.us/zf/page/"+str(page)      # 制服诱惑

if ms==1:
    url=url1
elif ms==2:
    url=url2
elif ms==3:
    url=url3
elif ms==4:
    url=url4
else:
    print('输入有误,请重新输入')
```

- 我感觉这部分不太优雅,这样写应该也一样:

```py
urls = ['xinggan', 'qc', 'yj', 'zf']
try:
    ms=int(input('请输入爬取内容:1、性感美女 2、清纯可爱 3、性感御姐 4、制服诱惑'))
    url = 'https://dimgw.us/' + urls
except ValueError:
    print('请输入一个整数,重新输入:')
except IndexError:
    print('输入有误,请重新输入:')
except:
    print('未知错误')
```

- 没看明白 `url1="https://dimgw.us/xinggan/page/"+str(page)` ,四个 url 字符串为什么要拼接一个 `str(page)`?而且 `page` 是个常量

# 2

- 我习惯把`requests`发送请求部分写成一个函数,像这个程序中有 3 次请求,第三次是下载图片,那我喜欢写成这样:

```py
def req(url, dire=True):                                        # dire 就是 direction 的简写,我发明的(自己造的)
    headersvalue = {}
    r = requests.get(url, headers=headersvalue)
    sc = BeautifulSoup(r.text, 'lxml') if True else r.content   # sc 就是 soup 和 content 的简写,我发明的(自己造的)
    return sc
```

# 3. for 循环中的一些细节

- `a_tag` 中每个元素下只有一个 `a` 节点,可以用 `find()` 方法,直接返回 `Tag` 对象,随取随用,不然 `find_all()` 方法返回一个列表,还要从列表中取出来,多此一举。不过,像下面那样 `str()`获得网址的方式,倒是无所谓。这个相信你也知道,不过我见看见了就说出来,显得内容多不是,嘿嘿

- 获取 `c_tag` 的方式有些独特,没见过也没想到的方式,竟然把 `Tag` 对象转成字符串,然后切片。初见有些惊讶,细想却感觉粗糙,一般不是通过属性值得到网址吗?我在网上见过的源码也都是这样做的。就像后面下载图片那部分你写的,用`get()`方法,不过下面注释里那条语句好像写错了,应该是 `href=b_tag.get('href')`。这样的话,合起来就是:`url = child_href.find('a')['href']`

- `bs4` 里获取节点属性的方法除了`get()`,还有 `Tag` 对象的`attrs`属性,这两天我又发现了一种更简便的方法,就是上面`url = child_href.find('a')['href']`中用的:`Tag[属性值]`。分享一下,不知道你知道不知道

- 下载图片时的命名问题(个人向):用 img 节点的 title 属性命名不好吗,图片网址中一堆字母和数字的名字没什么作用,传递不了什么有用的信息

- 用 with 语句打开文件,就不用用 close() 方法关闭文件了,会自动关上,python官方文档上有个地方有写

# 4

`while True`部分,最后的`page += 1`什么作用啊?这个循环体内好像没有和 page 这个变量有关的东西,整个 `while True`好像是个死循环。我没运行整个代码,但应该就是死循环,因为 VS Code 提示了,程序最后一行的 print 语句 Code is unreachable


# 总结

我按照自己的习惯,写了一个,讨论一下:(变量名尽量按照你的来,方便对照)

```py
import requests
from bs4 import BeautifulSoup

def req(url, dire=True):
    headersvalue = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36",
    "Referer":"https://dimgw.us/zf"
      }
    r = requests.get(url, headers=headersvalue)
    sc = BeautifulSoup(r.text, 'lxml') if dire else r.content
    return sc

urls = ['xinggan', 'qc', 'yj', 'zf']
try:
    ms=int(input('请输入爬取内容:1、性感美女 2、清纯可爱 3、性感御姐 4、制服诱惑'))
    url = 'https://dimgw.us/' + urls
except ValueError:
    print('请输入一个整数,重新输入:')
except IndexError:
    print('输入有误,请重新输入:')
except:
    print('未知错误')

a_tag = req(url)('h2',class_="entry-title")
for child_href in a_tag:
    c_tag = child_href.find('a')['href']
    div = req(c_tag).find('div',class_="entry-content u-text-format u-clearfix")('img')
    for it in div:
      img_url, img_name = it['src'], it['title']
      with open(f'd:/test/{img_name}.webp','wb') as f:
            f.write(req(img_url, dire=False))
      print(img_name + '下载完成')
print('下载完成')

```

MyModHeaven 发表于 2022-4-24 19:06

Ace803 发表于 2022-4-24 12:18
第一个问题,是我确实懒得写排除了,就是凑合一下,但是你写的代码比我漂亮多了,也学习了
第二个问题中 ...

这是我学 bs4 写的笔记,一起学习。Tag 对象的 attrs 属性里面有介绍


> https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/#
> https://beautifulsoup.cn/

- `beautifulsoup4`库也称为`Beautiful Soup`库或`bs4`库,用于解析 HTML 或 XML 文档

- `beautifulsoup4`库是一个第三方库

- `beautifulsoup4`库可以自动将输入文档转换为 Unicode 编码,将输出文档转换为 UTF-8 编码,故**不需要考虑编码方式**

- `beautifulsoup4`库在解析网页时需要依赖解析器,支持的解析器:

| 解析器                   | 使用方法                              | 优点                               | 缺点                           |
| --------------------- | ----------------------------------- | -------------------------------- | ------------------------------ |
| python 标准库中的 HTML 解析器 | BeautifulSoup(markup,'html.parser') | python 的内置标准库,执行速度适中,文档容错能力强   | python 2.73 或3.2.2 前的版本文档容错能力差 |
| lxml HTML 解析器         | BeautifulSoup(markup,'lxml')      | 速度快,文档容错能力强                      | 需要安装 C 语言库                     |
| lxml XML 解析器          | BeautifulSoup(markup,'xml')         | 速度快,唯一支持 XML 的解析器                | 需要安装 C 语言库                     |
| html5lib            | BeautifulSoup(markup,'html5lib')    | 容错性最好,以浏览器的方式解析文档,生成 HTML5 格式的文档 | 速度慢,不依赖外部扩展                  |

# 4 个对象

- beautiful soup 将复杂 HTML 文档转换成一个复杂的树形结构,每个节点都是 python 对象,所有对象可归纳为 4 种:Tag, NavigableString, BeautifulSoup, Comment

## Tag 对象

- 通俗来讲, Tag 就是 HTML 中的一个个标签

- 可以利用 `Tag 对象.标签名` 的方式获取标签的内容,查找的是所有内容中**第一个**符合要求的标签

- 可以利用 `Tag 对象[属性名]`的方式获取标签的属性值,返回一个 `list`

- Tag 对象有 4 个常用属性:

| 属性       | 说明                                          |
| -------- | --------------------------------------------- |
| name   | 标签的名字,如 head、title 等,返回一个字符串                  |
| string   | 标签所包围的文字,网页中真实的文字,返回一个字符串                     |
| attrs    | 字典,包含了页面标签的所有属性,返回一个字典,可通过`attrs['属性名']`获取属性值 |
| contents | 这个标签下所有**子标签**的内容(HTML 源码),返回一个列表             |

- 按照 HTML 语法,可以在标签中嵌套其他标签,因此`string`属性的返回值遵遵循如下原则:
1. 如果标签内部没有其他标签, string 属性返回其中的内容
2. 如果标签内部还有其他标签,但只有一个标签, string 属性返回最里面标签的内容
3. 如果标签内部还有其他标签,且不止一个标签, string 属性返回 None

此外,还有一些属性:

| 属性                | 说明      |
| ----------------- | --------- |
| children          | 返回所有的子节点|
| desecndants       | 返回所有子孙节点|
| parent            | 返回父节点   |
| parents         | 返回父节点的父节点 |
| next_siblings   |         |
| previous_siblings |         |

- .content 属性和 .childern 属性获取直接子节点,前者返回一个 list,后者返回一个 list 生成器对象,可以通过遍历获取所有子节点:

```py
for child in soup.body.children:
    print(child)
```

> .desecndants 属性也返回一个 list 生成器对象

## NavigableString 对象

- Tag 对象的 string 属性返回的内容就是 NavigableString 对象

## BeautifulSoup 对象

- BeautifulSoup 对象表示的是一个文档的全部内容。大部分时候可以当作 Tag 对象,是一个特殊的 Tag

## Comment 对象

- Comment 注释对象是一个特殊类型的 NavigableString 对象,其内容不包括注释符号

```py
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup('<b><!--This is comment--></b>')
>>> print(type(soup.find('b').string))
<class 'bs4.element.Comment'>
```

- 解析网页时:
1. 需要使用`BeautifulSoup()`创建一个`BeautifulSoup`对象
   
   > 该对象是一个树形结构,包含了 HTML 页面中的标签元素,如 <head>、<body> 等。也就是说,HTML 中的主要结构都变成了`BeautifulSoup`对象的一个个属性

2. 然后可通过`对象名.属性名`的形式获取该对象的**第一个属性值**(即节点)
   
   > `BeautifulSoup`对象的属性名与 HTML 的标签名相同

# 搜索文档树

## find_all

find_all(name, attrs, recursive, string, limit)

> find_all() 方法搜索当前 tag 的所有 tag 子节点,并判断是否符合过滤器的条件

1. name:通过 HTML 标签名直接查找节点。如果 name 参数是正则表达式,则通过正则表达式的 match() 来匹配内容
2. attrs:通过 HTML 标签的属性查找结点(需列出属性名和值),可以同时设置多个属性
3. recursive:搜索层次,默认查找当前标签的所有子孙节点,如果只查找标签的子节点,可以使用参数 `recursive=False`
4. string:通过关键字检索 string 属性内容,传入的形式可以是字符串,也可以是正则表达式对象
5. limit:返回结果的个数,默认返回全部结果

> `find_all()`方法通过属性查找结点时,对于一些常用的属性(如 id 和 class 等),可以不用 attrs 字典形式来传递,而用复制的形式直接传入参数(如`find_all(class_='hotnews')`,由于 class 在 python 中是一个关键字,所以后面需要加一个下划线

- 由于 find_all() 方法非常常用,所以在 bs4 中,BeautifulSoup 对象和 tag 对象可以被当作一个 find_all() 方法使用,即 bs.find_all('a') 和 bs('a') 是等效的,soup.title.find_all(text='abc') 和 soup.title(text='abc') 是等效的

```py
import requests
from bs4 import BeautifulSoup
import re
url = ''
r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
print('所有 span 节点个数:', len(soup.find_all('span')))
print('class 属性值为 “amount” 的所有 span 节点:')
for node in soup.find_all('span', attrs={'class': 'amount'}):
    print(node)
print('前 3 个 class 属性值为 “amount” 的 span 节点:\n', soup.find_all('span', attrs={'class': 'amount'}, limits=3))
print('string 属性值包含 “2室1厅” 的前三个节点的文本:', soup.find_all(string=re.compile('2室1厅'), limit=3))
```

## find

- `find()`方法的用法与`find_all()`方法相似,但其返回结果是第一个匹配的节点

## 其他查询方法

- `find_parents()`和`find_parent()`:前者返回所有祖先节点,后者返回直接父节点
- `find_next_siblings()`和`find_next_sibling()`:前者返回后面所有的兄弟节点,后者返回后面的第一个兄弟节点
- `find_previous_siblings()`和`find_previous_sibling()`:前者返回前面所有的兄弟节点,后者返回前面的第一个兄弟节点
- `find_all_previous()`和`find_previous()`:前者返回节点前所有符合条件的节点,后者返回节点前第一个符合条件的节点

## 多值属性

- HTML 中定义了一系列可以包含多个值的属性,最常见的多值属性是 class ,还有 rel, rev, accept-charset, headers, accesskey

- 在 Beautiful Soup 中多值属性的返回类型是 `list`

```py
soup = BeautifulSoup('<p class="body strikeout"></p>')
soup.p['class']
# ["body", "strikeout"]

soup = BeautifulSoup('<p class="body"></p>')
soup.p['class']
# ["body"]
```

- 如果某个属性值看起来好像有多个值,但在任何版本的 HTML 定义中都没有被定义为多值属性,那么 Beautiful Soup 会将这个属性组为字符串返回

```py
id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'
```

- `find_all()`和`find()`方法中,根据属性筛选节点时,若某个节点的多值属性的一个属性值与给出的属性值相同,也会出现在结果中

# CSS 选择器

- `beautifulsoup4`库还提供了使用 CSS 选择器来选择节点的方法(调用`select()`方法传入相应的 CSS 选择器,返回一个 list,用 get_text() 方法或 text 属性获取文本)

- CSS 常用的选择器:

| 选择器                | 示例            | 示例说明                              |
| ------------------ | --------------- | --------------------------------- |
| .class             | .intro          | 选择`class="intro"`的所有节点            |
| #id                | #firstname      | 选择`id="firstname"的所有节点            |
| *                  | *               | 选择所有节点                            |
| element            | p               | 选择所有`<p>`节点                     |
| element,element    | div,p         | 选择所有`<dic>`节点和所有`<p>`节点         |
| element element    | div p         | 选择`<div>`节点内部的所有`<p>`节点         |
| element>element    | div>p         | 选择父节点为`<div>`节点的所有`<p>`节点         |
|       |       | 选择带有 target 属性的所有节点               |
| | | 选择 target="blank" 的所有节点         |
| :link            | a:link          | 选择所有未访问的链接                        |
| :visited         | a:visited       | 选择所有已访问的链接                        |
| :active            | a:active      | 选择活动链接                            |
| :first-line      | p:first-line    | 选择每个`<p>`节点的首行                  |
| element1~element2| p~ul            | 选择前面有`<p>`节点的所有`<ul>`节点         |
| | a | 选择其 src 属性值以 “https” 开头的所有`<a>`节点 |
| | a| 选择其 src 属性值以 “.pdf” 结尾的所有`<a>`节点|
| | a   | 选择其 src 属性值中包含 “abc” 字串的所有`<a>`节点 |
| :enabled         | input:enabled   | 选择每个启用的`<input>`节点                |
| :disabled          | input:disabled| 选择每个禁用的`<input>`节点                |
| :checked         | input:checked   | 选择每个被选中的`<input>`节点               |

```py
import requests
from bs4 import BeautifulSoup
url = ''
r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
print('div 节点下所有 a 结点的个数:', len(soup.select('div a')))
# 获取第一个 calss 属性值为 “list-main-header” 的节点下所有 a 节点
node_a = soup.select('.list-main-header').select('a')
print('第一个 a 节点的 href 属性值:', node_a['href'])
print('第一个 a 节点的文本:', node_a.string)
```

# 实例

- 在 当当网 搜索 python,爬取该网页中图书的书名、作者、出版社和价格,保存到本地

```py
import requests
from bs4 import BeautifulSoup

def req(url):
    headersvalue = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.52'}
    r = requests.get(url, headers=headersvalue)
    soup = BeautifulSoup(r.text, 'lxml')
    return soup

url = 'http://search.dangdang.com/?key=python&act=input'
node_li = req(url).find('ul', {'class': 'bigimg'}).select('li')
book_detail = []
for li in node_li:
    name = li.a.attrs['title']
    author = li.find('p', class_='search_book_author').span.a.attrs['title']
    publisher = li.find('a', dd_name = '单品出版社').string
    price = li.find('span', class_='search_now_price').string
    book_detail.append('{}, {}, {}, {}'.format(name, author, publisher, price))

with open('d:/book_detail.txt', 'w', encoding='utf-8') as f:
    f.write('\n'.join(book_detail))
```

yuqilin234 发表于 2022-4-23 21:53

这个好好玩啊

414246704 发表于 2022-4-23 21:57

请问朋友是在哪里自学的,我也想学爬虫,你有好的教程推荐吗?

Ace803 发表于 2022-4-23 21:58

yuqilin234 发表于 2022-4-23 21:53
这个好好玩啊

从早上9点,到晚上9点多,写了两个爬虫,这是其中之一,另外一个是小说的爬虫,一天都在解决bug和报错了{:301_1008:}

Ace803 发表于 2022-4-23 22:02

414246704 发表于 2022-4-23 21:57
请问朋友是在哪里自学的,我也想学爬虫,你有好的教程推荐吗?

先在b站看的基础的,是杨淑娟老师讲的,挺好懂得,只是自己练习还是挺难的
然后又看的爬虫教学,看的头都大了
现在自己试着写些爬虫,头要爆炸了

winjie1975 发表于 2022-4-23 22:06

谢谢,辛苦了。顶一个。

wuaiwuai888 发表于 2022-4-23 22:16

这个网站良心啊,不用登陆都能直接下载压缩包

Ace803 发表于 2022-4-23 22:17

wuaiwuai888 发表于 2022-4-23 22:16
这个网站良心啊,不用登陆都能直接下载压缩包

是的,主要是练手{:301_995:}

8970665 发表于 2022-4-23 22:20

哦这东西蛮有趣的

adf28 发表于 2022-4-23 22:27

这个挺好的
页: [1] 2 3 4
查看完整版本: 新手--某美女图片爬取