Dust8 的博客

读书百遍其义自见

0%

Homebrew

官网: https://brew.sh/

安装软件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
brew install python3
brew install vim --with-python3

brew cask install google-chrome
brew cask install keka
brew cask install android-file-transfer
brew cask install android-platform-tools
brew cask install appcleaner
brew cask install iina
brew cask install qq

brew cask install teamviewer
brew cask install youdaodict
brew cask install youdaonote
brew cask install cheatsheet

brew cask install ccleaner
brew cask install mounty

起因

前不久在帮瓦工买了个 vps,把月付看成了年付,马上就过期了。
本着不浪费的精神想把流量用完,想下载 youtube 的视频来用完它。
you-get 是不错,可惜只能一个一个下。

获取所有下载的链接地址

在浏览器里打开审查,用 xpath 找出所有的视频链接。

1
2
3
var hrefs = $x(
'//ytd-playlist-video-renderer[@class="style-scope ytd-playlist-video-list-renderer"]//a[@is="yt-endpoint"]/@href'
);

打印出所有的视频链接

1
2
3
for (var i = 0, len = hrefs.length; i < len; i++) {
console.log(hrefs[i].textContent);
}

把链接复制到 youtube.txt 文件里面去。

用 bash 和 you-get 批量下载

1
2
3
4
cat youtueb.txt | while read line
do
you-get -s localhost:1086 https://www.youtuebe.com$line
done

今天在使用 requests 下载附件的时候出现 MissingHeaderBodySeparatorDefect 错误,感觉有点意思,记录下解决办法。

起因

我不知道要下载的文件名和后缀名,url 里面没有,文件名和后缀名只能从返回的响应头里找到,所以需要解析头部。

文件名是乱码

通过找资料知道默认的编码是 iso-8859-1 ,所以很简单的先编码在解码就搞定了。

1
res.headers['content-disposition'].encode('iso-8859-1').decode()

虽然是解决了问题,但是又碰到了一个开始说的问题

解析头部出错

去搜索出错信息,资料很少,最后发现一个相关的问题 HeaderParsingError: Failed to parse headers .
有人说不关 requests 的事,requests 解析头部用的是标准库里的库解析的。有人也给了解决方案,不过他说要放到 requests 里面去,其实只要在自己的代码里面重载就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def parse_headers(fp, _class=HTTPMessage):
"""Parses only RFC2822 headers from a file pointer.

email Parser wants to see strings rather than bytes.
But a TextIOWrapper around self.rfile would buffer too many bytes
from the stream, bytes which we later need to read as bytes.
So we read the correct bytes here, as bytes, for email Parser
to parse.

"""
headers = []
while True:
line = fp.readline(_MAXLINE + 1)
if len(line) > _MAXLINE:
raise LineTooLong("header line")
headers.append(line)
if len(headers) > _MAXHEADERS:
raise HTTPException("got more than %d headers" % _MAXHEADERS)
if line in (b'\r\n', b'\n', b''):
break
# 默认 iso-8859-1 编码。避免解析头部错误,用 utf8 编码
# hstring = b''.join(headers).decode('iso-8859-1')
hstring = b''.join(headers).decode()
return email.parser.Parser(_class=_class).parsestr(hstring)


http.client.parse_headers = parse_headers

怎么调试 xpath 的

不喜欢用正则,感觉用有时用 xpath 简单很多。

用到的一些 xpath 表达式

  • ‘a/text()’ 获取 a 元素的内容
  • ‘//tag[@attr0][@attr1]’

语法

选取节点

  • ‘//a/@href’ 获取所有 a 元素的 href 属性值
  • ‘//a/..’ 所有 a 元素的父元素

谓语

  • ‘//tr/td[1]’ 获取所有 tr 里面的第一个 td 元素,索引从 1 开始
  • ‘//li[@class=”p2”]’ 选择所有类名为 p2 的 li 元素
  • ‘//tr[td[@style]]’ 包含有 style 属性的 td 元素的所有 tr 元素
  • ‘li[a]’ li 元素下有 a 元素的 li 元素

选取若干路径

  • ‘a|h2’ a 元素或者 h2 元素

  • ‘//a/following-sibling::div[1]’ a 元素的第一个 div 兄弟元素
  • ‘//br/preceding-sibling::text()’ br 元素之前的所有同级的 text 节点.有些页面全是 br 元素换行,用它就可以取到和它同级的内容
  • ‘//a/ancestor::div[1]’ a 元素的第一个 div 父元素

运算符

  • ‘li[position() mod 2 = 0]’ 所有偶数位的 li 元素
  • ‘li[position() mod 2 = 1]’ 所有基数位的 li 元素
  • ‘//tr[len(td)>1]’ 获取有 1 个以上 td 元素的所有 tr 元素
  • ‘li[a or h2]’ li 元素下有 a 元素或者 h2 元素的 li 元素
  • ‘a[text()=”下一页”]’ text 为下一页的 a 元素

函数

有关字符串的函数

  • ‘string(.)’ 获取当前元素下面所有的内容
  • ‘string(//a)’ 获取 a 元素的所有文本(只能获取第一个元素的所有文本)
  • ‘//tr[contains(@class,”tr”)]’ 选择所有类名包含 tr 的 tr 元素
  • ‘//div[@id=”pagination”]/span[contains(text(),”共”)]’ 获取包含 ‘共’ 的 span 元素(用 starts-with 更好).我用来获取总页数(共 666 页)
  • ‘//span[starts-with(@class,”title”)]’ css 类名以 title 开头的 span 元素
  • ‘//p[starts-with(a,”亲”)]’ 包含以 ‘亲’ 开头的 a 元素的所有 p 元素
  • ‘//span[ends-with(@class,”title”)]’ css 类名以 title 结尾的 span 元素
  • ‘substring(//span[@class=”bugs-time”]/text(),6)’
  • ‘normalize-space(//td[starts-with(text(),”手”)]/following-sibling::td[1])’
  • ‘//div[normalize-space(string(.))=”工作经历”]’

关于布尔值的函数

  • ‘//ul[not(@class)]’ 选择所有没有类属性的 ul 元素

合计函数

  • ‘count(//a)’ 计算 a 元素的数量.可以用来统计一页有多少条数据
  • ‘//tr[count(td)=4]’ 只有只有 4ge 个 td 元素的 tr 元素

上下文函数

  • ‘//tr[position() > 1]’ 获取除了第一个 tr 元素之外的所有 tr 元素.有时第一个 tr 元素是抬头标签,不是数据
  • ‘//a[last()]’ 最后一个 a 元素.可以用来获取分页里面的最后一页,或者下一页

起因

要下载几千张图片,是 jpg 格式的,下下来之后才发现有部分图片
预览不了,显示损坏了。损坏的图片有的只有几个字节,有的和正常图片
有差不多的大小,所以不能根据文件大小来区分。

解决

根据文件头

python 标准库里面有个叫 imghdr 的模块,打开它的源代码
可以看到它是根据文件前面几个字节来判断是哪种格式。所以不行,有些图片有文件头,但是后面缺失了数据,它是无法判断的。

根据文件结束符

网上有说 jpg 尾部是 ff d9 , 我根据它来判断,还是不行,有一部分损坏的图片,它是有结束符的。

用PIL

网上有说用 pil 里面 imageverify() 来判断,还是不行,去看了下它的代码,它主要针对的是 png 格式的。后来有人说用
load() , 试了下可以。代码如下:

1
2
3
4
5
6
7
8
9
10
from PIL import Image


def is_valid_image(filename):
valid = True
try:
Image.open(filename).load()
except OSError:
valid = False
return valid

总结

要多读好的代码,比如说 imghdr 这是标准库里面的模块,以前从来没听说过,也没去看过它。它的代码很短,也很简单,很适合我们新手学习。
比去网上看别人的文章还要好。网上充满了千篇一律的东西,我们应该好好的看一看标准库里面的代码。先挑简单的来,在慢慢看大一点,复杂一点的模块。学 python 这么久还是新手,主要就是不会思考,不会学习。

在这里我以过来人的身份给大家的忠告是:要读标准库里的源代码