m.mzitu.com爬取

m.mzitu.com爬取

该篇尺度较大,慎入!
之前最早用Stata爬过这个网址而且还完整地运行了一遍,大概下载到了10万张图片,昨天又尝试用Python写了一个程序爬取该网站的所有图片。

刚刚突然发现这个网站 http://www.mzitu.com/ 和我爬的 http://m.mzitu.com/ 其实是一样的,而且显然 http://m.mzitu.com/ 更容易爬取!就先这样吧,下面开始介绍我的这个程序实现的方法。

Stata程序

之前的Stata爬虫程序已经出问题了,不过我还是把它复制过来吧,可以作为参考。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
clear all
cap mkdir "~/Desktop/m_mzitu"
cd "~/Desktop/m_mzitu"
/*该网站一个168页,每页有好几个妹子
第一页和最后一页的网址为:
http://m.mzitu.com/page/1/
http://m.mzitu.com/page/168/
然后每一页有很多妹子的封面
*/
** 第一步:先爬所有妹子的链接和名字
copy "http://m.mzitu.com/" temp.txt, replace
cap unicode encoding set gb18030
cap unicode translate temp.txt
cap unicode erasebackups, badidea
infix strL v 1-20000 using temp.txt, clear
keep if index(v, "<h2>")
replace v = subinstr(v, "<h2>", "", .)
replace v = subinstr(v, "</a></h2>", "", .)
gen url = ustrregexs(0) if ustrregexm(v, `"http+(.*)[0-9]\""')
replace url = subinstr(url, `"""', "", .)
gen name = ustrregexs(1) if ustrregexm(v, ">(.*)")
replace name = subinstr(name, " ", "", .)
drop v
compress
save 1.dta, replace
cap erase temp.txt
forvalues i = 2/168 {
copy "http://m.mzitu.com/page/`i'/" temp.txt, replace
cap unicode encoding set gb18030
cap unicode translate temp.txt
cap unicode erasebackups, badidea
infix strL v 1-20000 using temp.txt, clear
keep if index(v, "<h2>")
replace v = subinstr(v, "<h2>", "", .)
replace v = subinstr(v, "</a></h2>", "", .)
gen url = ustrregexs(0) if ustrregexm(v, `"http+(.*)[0-9]\""')
replace url = subinstr(url, `"""', "", .)
gen name = ustrregexs(1) if ustrregexm(v, ">(.*)")
replace name = subinstr(name, " ", "", .)
drop v
compress
local j = `i'-1
append using `j'.dta
erase `j'.dta
save `i'.dta, replace
cap erase temp.txt
}
replace name = subinstr(name, ".", "", .)
save 168, replace

count
ret list
local N = `r(N)'
* 一共是4018个妹子

use 168.dta, clear
gen n = _n
gsort -n
drop n
qui{
preserve
local url = "`=url[1]'"
local name = "`=name[1]'"
cap mkdir "`name'"
qui cd "`name'"
copy "`url'" "temp.txt", replace
infix strL v1 1-20000 using temp.txt, clear
keep if (index(v1, "alt") & index(v1, "</a></p>")) | index(v1, "prev-next-page")
replace v1 = ustrregexs(0) if ustrregexm(v1, `"http://i+(.*)jpg\""')
replace v1 = ustrregexs(1) if ustrregexm(v1, "\/([0-9]+)页+")
replace v1 = subinstr(v1, `"""', "", .)
local w = v1[1]
!curl `w' -e http://m.mzitu.com/ -o `name'_1.jpg
cap erase temp.txt
drop in 1
destring v1, replace
local page = `=v1[1]'
}
di "正在下载《`name'》,一共`page'页,已下完第1页"
forvalues j = 2/`page' {
cap qui{
copy "`url'/`j'" temp.txt, replace
infix strL v1 1-20000 using temp.txt, clear
keep if index(v1, "alt") & index(v1, "</a></p>")
replace v1 = ustrregexs(0) if ustrregexm(v1, `"http://i+(.*)[a-z]\""')
replace v1 = subinstr(v1, `"""', "", .)
local w = v1[1]
!curl `w' -e http://m.mzitu.com/ -o `name'_`j'.jpg
cap erase "temp.txt"
}
di "正在下载《`name'》,一共`page'页,已下完第`j'页"
}
restore
forvalues m = 2/`=_N'{
local jd = (100*`m')/4018
di "###############已完成`jd'%#################"
cd "~/Desktop/m_mzitu"
cap qui{
preserve
local url = url[`m']
local name = name[`m']
cap mkdir "`name'"
qui cd "`name'"
copy `url' temp.txt, replace
infix strL v1 1-20000 using temp.txt, clear
keep if (index(v1, "alt") & index(v1, "</a></p>")) | index(v1, "prev-next-page")
replace v1 = ustrregexs(0) if ustrregexm(v1, `"http://i+(.*)jpg\""')
replace v1 = ustrregexs(1) if ustrregexm(v1, "\/([0-9]+)页+")
replace v1 = subinstr(v1, `"""', "", .)
local w = v1[1]
!curl `w' -e http://m.mzitu.com/ -o `name'_1.jpg
cap erase "1_`m'.txt"
drop in 1
destring v1, replace
local page = v1[1]
}
di "正在下载《`name'》,一共`page'页,已下完第1页"
forvalues j = 2(1)`page' {
cap qui{
copy "`url'/`j'" temp.txt, replace
infix strL v1 1-20000 using temp.txt, clear
keep if index(v1, "alt") & index(v1, "</a></p>")
replace v1 = ustrregexs(0) if ustrregexm(v1, `"http://i+(.*)jpg\""')
replace v1 = subinstr(v1, `"""', "", .)
local w = v1[1]
!curl `w' -e http://m.mzitu.com/ -o `name'_`j'.jpg
cap erase "temp.txt"
}
di "正在下载《`name'》,一共`page'页,已下完第`j'页"
}
restore
}
cap erase temp.txt

记得运行成功的那次也只得到了4081组图,总计40万张图片,收藏欲获得了极大的满足感啊!
从程序中可以看出上面的Stata程序的思路是下面这样的:

  • 首先遍历 http://m.mzitu.com/page/1/http://m.mzitu.com/page/168/ (当时这个网站只要168页),获取所有妹子套图的链接;
  • 然后就得到了4081个链接,例如 http://m.mzitu.com/3583
  • 再然后遍历每个套图的首页到尾页, 例如: http://m.mzitu.com/3583/1http://m.mzitu.com/3583/5 ;获取每个页面的主图片链接并下载到根据该组套图标题创建的子文件夹中。
  • 最后需要注意的是该网站设置了防盗链,因此直接copy是copy不下来的,需要使用curl工具模拟浏览器请求,而且只需要设置跳转参数即可,例如:!curl http://i.meizitu.net/2013/06/bda3bfadgw1e2w2srk8t9j.jpg -e http://m.mzitu.com/ -o test.jpg

Python程序

显然我的Stata程序设计的是由外到里的,而下面我设计的Python程序是由里到外的。

全局取消证书验证

由于Python 升级到 2.7.9 之后引入了一个新特性,当使用urllib.urlopen打开一个 https 链接时,会验证一次 SSL 证书。而当目标网站使用的是自签名的证书时就会抛出此异常。因此需要使用下面的代码:

1
2
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

图片下载函数

由于该网站设置了防盗链,所以需要模拟浏览器进行图片下载的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def download_pic(url, name):
import requests
headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) " \
"A®ppleWebKit/537.36 (KHTML, like Gecko) " \
"Chrome/66.0.3359.181 Safari/537.36",
"Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
"Referer": "http://m.mzitu.com/138401",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"}
res = requests.get(url, headers=headers)
res.raise_for_status()
playFile = open(name, 'wb')
for chunk in res.iter_content(100000):
playFile.write(chunk)
playFile.close()

根据每组图的首页链接获取所含图片链接的函数

首先编写一个根据某套妹子图主页链接判断该套图的页数的函数

1
2
3
4
5
6
7
8
9
10
11
def get_page(url):
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
html = urlopen(url)
bsObj = BeautifulSoup(html, "html.parser")
page_temp = bsObj.findAll("span", {"class":"prev-next-page"})
for p in page_temp:
page = re.findall(r"1/(.+?)页", p.get_text())
print("该套图一共",page[0],"页")
return int(page[0])

编写一个函数获取一套图的所有链接

1
2
3
4
5
6
7
8
9
10
11
def get_pic_link(url):
from bs4 import BeautifulSoup
from urllib.request import urlopen
link = []
for i in range(1, get_page(url)):
url_temp = url + "/" + str(i)
html = urlopen(url_temp)
bsObj = BeautifulSoup(html, "html.parser")
for j in bsObj.figure.findAll("img"):
link.append(j.attrs["src"])
return link

获取每个妹子的套图链接的函数并输出为一个mzitu.csv文件

这一次很棒的就是发现这个网站其实使用10191组图!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def get_all_link():
from bs4 import BeautifulSoup
from urllib.request import urlopen
import re
import pandas as pd
link = []
name = ['url']
for i in range(1, 183):
url = "http://m.mzitu.com/page/" + str(i) + "/"
html = urlopen(url)
bsObj = BeautifulSoup(html, "html.parser")
link_list = bsObj.findAll("a", {"href": re.compile("http://m.mzitu.com/[0-9]")})
for m in link_list:
if m not in link:
link.append(m.attrs["href"])
print("第"+str(i)+"页完成!")
print("一共%d组图片" % len(link))
test = pd.DataFrame(columns = name, data = link)
test.to_csv("mzitu.csv")
return link

根据一个套图链接返回其名称

1
2
3
4
5
6
7
8
9
def get_title(url):
from bs4 import BeautifulSoup
from urllib.request import urlopen
html = urlopen(url)
bsObj = BeautifulSoup(html, "html.parser")
title = ""
for i in bsObj.findAll("h2", {"class":"blog-title"}):
title = i.get_text()
return title

一个根据给定字符串创建字文件夹并设定子文件夹为工作目录的函数

1
2
3
4
5
6
7
8
9
10
11
12
def make_and_cd(foldername):
import os
def mkdir(path):
folder = os.path.exists(path)
if not folder: # 判断是否存在文件夹如果不存在则创建为文件夹
os.makedirs(path) # makedirs 创建文件时如果路径不存在会创建这个路径
print("--- 创建新文件夹... ---")
print("--- 创建完成 ---")
else:
print("--- 文件夹已存在! ---")
mkdir(foldername)
os.chdir(foldername)

开始全站爬取

1
2
3
4
5
6
7
8
9
10
11
import os
os.chdir("/Users/mr.cheng/Desktop/Python网络数据采集")
cwd = os.getcwd()
for i in get_all_link():
make_and_cd(get_title(i))
m = 1
for j in get_pic_link(i):
download_pic(j, name = str(m)+".jpg")
m += 1
print("下载成功")
os.chdir(cwd)

爬取结果

设计好之后我是运行了一下,中间出错了,估计是网站有些地方结构变化了。下面是测试运行的结构:
mzitu爬取结果
非常大尺度了。。。

附录

当然这个项目仅供娱乐,在解决问题的过程中学习到了很多东西,下面再进行一个总结。

BeautifulSoup()函数运行出错

最开始我是按照《Python网络数据搜集》这本书上的介绍使用bsObj = BeautifulSoup(html) 解析网页结构,但是出错了,根据这篇博客NaomiEdna的博客的介绍,需要修改成这个才不会出错:bsObj = BeautifulSoup(html, "html.parser")

使用动态文件名

下载文件的时候需要为每个文件分配一个文件名,因此保证文件名不重复实际上是一个大问题,这个网站用python创建文件,文件名编号自动递增。上提供了一种使用时间作为文件名的方法,不过我没有使用,而那个 downpic.py使用了这种方法:

1
2
3
import time
localTime = time.strftime("%Y%m%d%H%M%S",time.localtime())
filename = localTime+".jpg"

不过用这个方法有时候时间设置到秒还是不够的,可以再设置一个递增的变量作为补充。

数值转字符串

这篇博客介绍了不同类型变量的转换:python中的字符数字之间的转换函数

1
2
i = 10
print(str(i))

Python异常处理-跳过异常继续执行

这篇博客介绍了异常的跳过处理:Python异常处理-跳过异常继续执行。开始担心出现异常,于是想用,但是短时间测试运行的时候没有异常,所以就没有用,没想到还是有异常。

创建文件和文件夹

这篇博客介绍了如果创建文件和文件夹:。感谢这篇文章我才写出了创建文件夹和修改工作目录的函数:

创建文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os  
def mkdir(path):
folder = os.path.exists(path)
if not folder: #判断是否存在文件夹如果不存在则创建为文件夹
os.makedirs(path) #makedirs 创建文件时如果路径不存在会创建这个路径
print "--- new folder... ---"
print "--- OK ---"
else:
print "--- There is this folder! ---"
file = "G:\\xxoo\\test"
mkdir(file) #调用函数

# os.getcwd()可以查看py文件所在路径;
# 在os.getcwd()后边 加上 [:-4] + 'xxoo\\' 就可以在py文件所在路径下创建 xxoo文件夹
folder = os.getcwd()[:-4] + 'new_folder\\test\\'
#获取此py文件路径,在此路径选创建在new_folder文件夹中的test文件夹
if not os.path.exists(folder):
os.makedirs(folder)

创建new.txt文件

1
2
3
import os  
file = open('C:\\Users\Administrator\\Desktop\\' + 'new' + '.txt','w')
file.close()

在py文件路径下创建test的txt文件

1
2
3
4
5
6
7
8
9
10
11
12
import os  

def txt(name,text): #定义函数名
b = os.getcwd()[:-4] + 'new\\'
if not os.path.exists(b): #判断当前路径是否存在,没有则创建new文件夹
os.makedirs(b)
xxoo = b + name + '.txt' #在当前py文件所在路径下的new文件中创建txt
file = open(xxoo,'w')
file.write(text) #写入内容信息
file.close()
print ('ok')
txt('test','hello,python') #创建名称为test的txt文件,内容为hello,python

创建Excel

1
2
3
4
5
6
7
8
9
10
11
12
import xlsxwriter  


workbook = xlsxwriter.Workbook('G:\\xxoo\\103.xlsx')
#在G盘xxoo文件下创建103的excel
worksheet = workbook.add_worksheet('s001')
#103的excel的sheet页名称为s001
worksheet.write(0,0,123456)
worksheet.write(2,1,664)
worksheet.write(1,5,250)
#写入信息
workbook.close()

使用xlwt也可以创建

1
2
3
4
5
6
7
import xlwt  
wb = xlwt.Workbook()
ws = wb.add_sheet('s001')
ws.write(0,0,452)
ws.write(1,4,6868)
ws.write(2,3,6666)
wb.save('C:\\Users\\Administrator\\Desktop\\103.xlsx')

在python中如何设置当前工作目录

在python中如何设置当前工作目录

1
2
3
4
5
6
import os
os.getcwd()
'D:\\Python27'
os.chdir('d:\\aa')
os.getcwd()
'd:\\aa'

正则表达式提取字符串

python利用正则表达式提取字符串

单个位置的字符串提取

1
2
3
4
import re
str = "a123b"
print re.findall(r"a(.+?)b",str)#
输出['123']
贪婪和非贪婪匹配
1
2
3
4
5
6
7
8
import re
str = "a123b456b"
print re.findall(r"a(.+?)b", str)
#输出['123']#?控制只匹配0或1个,所以只会输出和最近的b之间的匹配情况
print re.findall(r"a(.+)b", str)
#输出['123b456']
print re.findall(r"a(.*)b", str)
#输出['123b456']
多行匹配

如果你要多行匹配,那么需要加上re.S和re.M标志. 加上re.S后。将会匹配换行符,默认.不会匹配换行符。

1
2
3
4
5
6
str = "a23b\na34b"
re.findall(r"a(\d+)b.+a(\d+)b", str)
#输出[]
#因为不能处理str中间有\n换行的情况
re.findall(r"a(\d+)b.+a(\d+)b", str, re.S)
#s输出[('23', '34')]

加上re.M后,^$标志将会匹配每一行,默认^$只会匹配第一行

1
2
3
4
5
str = "a23b\na34b"
re.findall(r"^a(\d+)b", str)
#输出['23']
re.findall(r"^a(\d+)b", str, re.M)
#输出['23', '34']

连续多个位置的字符串提取

这种情况我们可以使用(?P…)这个正则表达式来提取。举例,如果我们有一行webserveraccess日志:’192.168.0.1 25/Oct/2012:14:46:34 “GET /api HTTP/1.1” 200 44 “http://abc.com/search" “Mozilla/5.0”‘,我们想提取这行日志里面所有的内容,可以写多个(?Pexpr)来提取,其中name可以更改为你为该位置字符串命名的变量,expr改成提取位置的正则即可。
代码为:

1
2
3
4
5
6
7
8
9
10
import re
line ='192.168.0.1 25/Oct/2012:14:46:34 "GET /api HTTP/1.1" 200 44 "http://abc.com/search"
"Mozilla/5.0"'
reg = re.compile('^(?P<remote_ip>[^ ]*) (?P<date>[^ ]*) "(?P<request>[^"]*)"
(?P<status>[^ ]*) (?P<size>[^ ]*) "(?P<referrer>[^"]*)" "(?P<user_agent>[^"]*)"')
regMatch = reg.match(line)
linebits = regMatch.groupdict()
print linebits
for k, v in linebits.items() :
print k+": "+v

结果为:

1
2
3
4
5
6
status: 200
referrer:
request: GET /api HTTP/1.1
user_agent: Mozilla/5.0
date: 25/Oct/2012:14:46:34size: 44
remote_ip: 192.168.0.1

Python判断一个字符串是否包含子串的几种方法

Johnson_Yuan
1.使用成员操作符 in

1
2
3
4
5
>>> s='nihao,shijie'
>>> t='nihao'
>>> result = t in s
>>> print result
True

2.使用string模块的find()/rfind()方法

1
2
3
4
5
6
7
8
9
>>> import string
>>> s='nihao,shijie'
>>> t='nihao'
>>> result = string.find(s,t)!=-1
>>> print result
True
>>> result = string.rfind(s,t)!=-1
>>> print result
True

3.使用string模块的index()/rindex()方法
index()/rindex()方法跟find()/rfind()方法一样,只不过找不到子字符串的时候会报一个ValueError异常。

1
2
3
4
5
6
7
8
9
10
11
import string
def find_string(s,t):
try:
string.index(s,t)
return True
except(ValueError):
return False
s='nihao,shijie'
t='nihao'
result = find_string(s,t)
print result #True

4.使用字符串对象的find()/rfind()、index()/rindex()和count()方法

1
2
3
4
5
6
7
8
9
10
11
>>> s='nihao,shijie'
>>> t='nihao'
>>> result = s.find(t)>=0
>>> print result
True
>>> result=s.count(t)>0
>>> print result
True
>>> result=s.index(t)>=0
>>> print result
True

Python网络编程 - 请求地址上的文件并下载

Python网络编程-请求地址上的文件并下载

1
2
3
4
5
6
7
import requests
res = requests.get('http://www.gutenberg.org/cache/epub/1112/pg1112.txt')
res.raise_for_status()
playFile = open('RomeoAndJuliet.txt', 'wb')
for chunk in res.iter_content(100000):
playFile.write(chunk)
playFile.close()

# Python

评论

程振兴

程振兴 @czxa.top
截止今天,我已经在本博客上写了604.4k个字了!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×