这次玩点刺激的,爬取我的所有博客。
当然,这事儿只有我能干,你们要爬可以爬自己的,后面我会把代码和分析结果放出来。
这两周发生了些不太愉快的事情,反正我现在是挺失望的。
刚开始呢,我想找网站地图,看看能不能找到属于我的那一块儿。后来发现是我想多了,网站地图是有,但是那么多博主,一人搞一个也不太现实。于是这条路就走不通了。
接下来,我又去了“文章管理”界面,但是我马上就发现了这是一个动态网页。
我看了看底部的页码,十五页,说多页多,说少也少。反正就挺尴尬一个数的。
我想了想,这个页面比主页要简单点,抓个包看看吧。
找到了文章ID的包,发现网址单独拿出来打不开,于是又放弃了。
最后,我又回到了主页。
底部的页码一看,七页,可以,动手吧。
我本来想着,链接和标题一起拿了,后来转念一想,,文章里面也是有标题的,到时候一起拿就好了。
于是开始写代码。
有一说一啊,取Xpath的时候,谷歌确实好用,用火狐取出来的Xpath一直放空,谷歌取出来的是相对Xpath,一步到位。
import requests
import threadpool
from lxml import etree
import pandas as pd
cookie = '放你自己的'
header = {
'User-Agent': '放你自己的',
'Connection': 'keep-alive',
'accept': 'application/json, text/javascript, */*; q=0.01',
'Cookie': cookie,
'referer': '放你自己的主页'
}
url_list = ['https://lion-wu.blog.csdn.net/article/list/1', 'https://lion-wu.blog.csdn.net/article/list/2', 'https://lion-wu.blog.csdn.net/article/list/3', 'https://lion-wu.blog.csdn.net/article/list/4', 'https://lion-wu.blog.csdn.net/article/list/5', 'https://lion-wu.blog.csdn.net/article/list/6', 'https://lion-wu.blog.csdn.net/article/list/7'] # 这个链接很有规律的
keep_url_list = [] # 这个用来
def outdata(url):
try:
print('succeed'+url)
res = requests.get(url,headers=header)
wbdata = res.content.decode('UTF-8')
tree = etree.HTML(wbdata)
el_list = tree.xpath('//*[@id="articleMeList-blog"]/div[2]//div/h4/a/@href')
print(el_list)
keep_url_list.append(el_list)
except:
print('failed'+url)
def Thread_Pool(outdata,datalist = None,Thread_num = 5):
'''
线程池操作,创建线程池、规定线程池执行任务、将任务放入线程池中、收工
:param outdata: 函数指针,线程池执行的任务
:param datalist: 给前面的函数指针传入的参数列表
:param Thread_num: 初始化线程数
:return: 暂无
'''
pool = threadpool.ThreadPool(Thread_num) # 创建Thread_num个线程
tasks = threadpool.makeRequests(outdata, datalist) # 规定线程执行的任务
# outdata是函数名,datalist是一个参数列表,线程池会依次提取datalist中的参数引入到函数中来执行函数,所以参数列表的长度也就是线程池所要执行的任务数量。
[pool.putRequest(req) for req in tasks] # 将将要执行的任务放入线程池中
pool.wait() # 等待所有子线程执行完之后退出
Thread_Pool(outdata,datalist=url_list,Thread_num = 7)
#outdata('https://lion-wu.blog.csdn.net/article/list/1')
u2 = []
for i in keep_url_list:
for j in i:
print(j)
u2.append(j)
pd.DataFrame(u2).to_csv('My_CSDN.csv')
本文使用测试文档:测试文档,要自己动手实现的朋友请打开测试文档跟着操作。
随便点开了一篇博客的源码,看到里面不同的部件有不同的标签。
那么这里就涉及到了三个问题:
1、我总共用了多少不同的效果?
2、在爬取的时候,如何使不同的标签下的数据在存储的时候保持原有的顺序
3、标签的标记是否需要留下
第一个问题好办,打开编辑界面就可以很清楚的看到所有的效果了:
回忆一下我用过的所有效果,有:
文章标题、文内标题、(目录)、加黄标、加粗、斜体、无序、有序、待办、【引用】、【代码块】、【图片】、【表格】、【超链接】、【分隔线】
打括号的是不要的,打中括号的是常用的。
那,要怎么看这些效果在源码里的体现呢,去找是不可能去找的了,写一篇博客,把这些功能都包进去测试就好了。
对于问题二啊,我也纠结了一会儿,因为我不知道Xpath在爬取多个不同标签的时候能否保留住他们原有的顺序。
百度了一会儿,说真的,全是屁话。
于是我就做了个demo测试了一下:
import requests
from lxml import etree
# 前面这一串不再放出
def outdata(url):
try:
print('succeed'+url)
res = requests.get(url,headers=header)
wbdata = res.content.decode('UTF-8')
tree = etree.HTML(wbdata)
el_list = tree.xpath('//*[@id="articleMeList-blog"]/div[2]//div/h4/a')
for el in el_list:
e = el.xpath('./text() | ./@href')
# 我特地把顺序反过来,就是要排除这种可能,因为真的开始爬的时候是不会事先让你知道顺序规律的,也没有规律可言。
print(e)
except:
print('failed'+url)
outdata('https://lion-wu.blog.csdn.net/article/list/1')
结果证明是成功的,再做点字符串切割切掉转义字符和前后空格就行了。
本来以为这个问题是最简单的,只是我想不想留的问题。后来发现不是这样的。
对于这个问题,如果直接上手去抓标签里面的文本的话,最终是会丢失掉标签的。
这个问题我想了想,我们可以先将文章标题取下,
之后取下文章正文部分的全部源码,用正则表达式对源码中的各标签打上标记,
之后再用Xpath将文本和链接取出来。
结果:转成字符串之后转不回来了、、、
于是,我又产生了一个想法。
首先,非硬性需求的特效就不要了。比方说加粗、斜体、黄标、下划线这种的,就不要了。无序,有序,待办归为一类,也不要了。
这样一选择,那么需要注意的特效(单独再提取一份出来作为标记)就只有:引用、代码块、图片、表格、超链接了。
引用,代码块只标记首尾,表格把表头取出之后底下的也只标记首尾,
超链接和图片链接需要拿出来。
剩下的就交给匹配算法的事情了。
就是说,先把文本和链接全部提取出来,再重头提取一些重要信息。
这个只是复杂度高一些,实现还是没问题的。
在Xpath提取的时候,看看能不能直接对文本进行标记,如果可以的话,那就最好。
我选三,实现了。
方法一里面不是有说,将etree对象转化为字符串吗?
那我完全可以先把标签都选下来,我不取文本,我直接转字符串,这样不就连标签带文本全拿下来了吗?最后我们通过正则表达式将HTML代码中很长的标签转换为比较短的标签。
来看一下从测试文档上抓下来的标签们:
def outdata(url):
try:
print('succeed'+url)
res = requests.get(url,headers=header)
code = res.apparent_encoding # 获取url对应的编码格式
res.encoding = code
wbdata = res.text
tree = etree.HTML(wbdata)
el_list = tree.xpath('//*[@id="content_views"]')
for el in el_list:
# e = etree.tostring(el, encoding=code).decode(code)
# 这一步可以获取文章主体的源码部分
#界面xpath在下面有提供
es = el.xpath('./h1 |./h2 |./h3 |./h4 |./h5 |./h6 |./p |./p/mark |./p/span/span/span/span[2]//span/span[2]'
'|./p/strong |./p/em |./ul//li |./ol//li |./ul//li |./blockquote/p |./pre/code |./p/code '
'|./div/table/thead/tr//th |./div/table/tbody/tr//td |./hr |./p/img |./p/a')
for e in es:
print(etree.tostring(e, encoding=code).decode(code))
print('-----') # 调试所用,使得结果更清晰
except:
print('failed'+url)
结果:
succeedhttps://lion-wu.blog.csdn.net/article/details/113402976
<p/>
-----
<p/>
-----
<h1><a id="_2"/>一级标题</h1>
-----
<h2><a id="_3"/>二级标题</h2>
-----
<h3><a id="_4"/>三级标题</h3>
-----
<h4><a id="_5"/>四级标题</h4>
-----
<h5><a id="_6"/>五级标题</h5>
-----
<h6><a id="_7"/>六级标题</h6>
-----
<p>这是一篇测试文档,现在不知道干嘛用很正常,我在写一个爬虫的项目,等我爬虫自学系列最后一篇出来就知道啦,到时候如果你们想复现的话,直接来我这里拿就好。</p>
-----
<p><span class="katex--display"><span class="katex-display"><span class="katex"><span class="katex-mathml">
a
=
b
+
c
a = b + c
</span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.43056em; vertical-align: 0em;"/><span class="mord mathdefault">a</span><span class="mspace" style="margin-right: 0.277778em;"/><span class="mrel">=</span><span class="mspace" style="margin-right: 0.277778em;"/></span><span class="base"><span class="strut" style="height: 0.77777em; vertical-align: -0.08333em;"/><span class="mord mathdefault">b</span><span class="mspace" style="margin-right: 0.222222em;"/><span class="mbin">+</span><span class="mspace" style="margin-right: 0.222222em;"/></span><span class="base"><span class="strut" style="height: 0.43056em; vertical-align: 0em;"/><span class="mord mathdefault">c</span></span></span></span></span></span></p>
-----
<span class="mord mathdefault">a</span>
-----
<span class="mord mathdefault">b</span>
-----
<span class="mord mathdefault">c</span>
-----
<p><mark>这是突出字体</mark></p>
-----
<mark>这是突出字体</mark>
-----
<p><strong>这是加粗字体</strong></p>
-----
<strong>这是加粗字体</strong>
-----
<p><em>这是斜体</em></p>
-----
<em>这是斜体</em>
-----
<li>这是无序</li>
-----
<li>这还是无序</li>
-----
<hr/>
-----
<li>这是有序</li>
-----
<li>这还是有序</li>
-----
<hr/>
-----
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/> 这是待办</li>
-----
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/> 这依旧是待办</li>
-----
<li>这是有序</li>
-----
<li>这还是有序</li>
-----
<hr/>
-----
<p>这里是引用<br/> 这里还是引用</p>
-----
<code class="prism language-python">代码块在这里
</code>
-----
<hr/>
-----
<p>这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用</p>
-----
<code class="prism language-python">代码块在这里
</code>
-----
<p><img src="https://img-blog.csdnimg.cn/20210129182417155.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNzYyMTkx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"/></p>
-----
<img src="https://img-blog.csdnimg.cn/20210129182417155.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNzYyMTkx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"/>
-----
<p><a href="https://blog.csdn.net/qq_43762191?spm=1001.2101.3001.5343">超链接,顺着网线来打我啊!!!</a></p>
-----
<a href="https://blog.csdn.net/qq_43762191?spm=1001.2101.3001.5343">超链接,顺着网线来打我啊!!!</a>
-----
<hr/>
-----
<p>别忘了下划线哦<code>这是行代码</code></p>
-----
<code>这是行代码</code>
-----
分析一下这里的结果,我们才好对下一步进行决策嘛。
1、首先,第一眼就看到了那一大串标签围绕的公式了,我不记得我还有没有带公式的博客,就留着吧,反正也是一个正则的事情。
2、其次一个很明显的就是重复问题了。
之前直接提取文本的时候不会出现,因为‘/’仅仅提取当前子路径下的所有,但是现在转了字符串,那么‘./p’就成了很多个以‘./p’开头的标签的上级标签了。这时候重复的出现就是必然的了。
在取标签的时候,这似乎是不可调和的矛盾,那就只好在取出标签之后进行一次去重了。
所以我还得写一个去重的函数
3、对于上面这个问题,还有一个解决方法,即在取标签的时候,对于所有以‘./p/’开头的标签全部不留,只留下‘./p’,后面取标签的时候将
再看一下效果。
succeedhttps://lion-wu.blog.csdn.net/article/details/113402976
<p/>
-----
<p/>
-----
<h1><a id="_2"/>一级标题</h1>
-----
<h2><a id="_3"/>二级标题</h2>
-----
<h3><a id="_4"/>三级标题</h3>
-----
<h4><a id="_5"/>四级标题</h4>
-----
<h5><a id="_6"/>五级标题</h5>
-----
<h6><a id="_7"/>六级标题</h6>
-----
<p>这是一篇测试文档,现在不知道干嘛用很正常,我在写一个爬虫的项目,等我爬虫自学系列最后一篇出来就知道啦,到时候如果你们想复现的话,直接来我这里拿就好。</p>
-----
<p><span class="katex--display"><span class="katex-display"><span class="katex"><span class="katex-mathml">
a
=
b
+
c
a = b + c
</span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.43056em; vertical-align: 0em;"/><span class="mord mathdefault">a</span><span class="mspace" style="margin-right: 0.277778em;"/><span class="mrel">=</span><span class="mspace" style="margin-right: 0.277778em;"/></span><span class="base"><span class="strut" style="height: 0.77777em; vertical-align: -0.08333em;"/><span class="mord mathdefault">b</span><span class="mspace" style="margin-right: 0.222222em;"/><span class="mbin">+</span><span class="mspace" style="margin-right: 0.222222em;"/></span><span class="base"><span class="strut" style="height: 0.43056em; vertical-align: 0em;"/><span class="mord mathdefault">c</span></span></span></span></span></span></p>
-----
<p><mark>这是突出字体</mark></p>
-----
<p><strong>这是加粗字体</strong></p>
-----
<p><em>这是斜体</em></p>
-----
<li>这是无序</li>
-----
<li>这还是无序</li>
-----
<hr/>
-----
<li>这是有序</li>
-----
<li>这还是有序</li>
-----
<hr/>
-----
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/> 这是待办</li>
-----
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/> 这依旧是待办</li>
-----
<li>这是有序</li>
-----
<li>这还是有序</li>
-----
<hr/>
-----
<p>这里是引用<br/> 这里还是引用</p>
-----
<code class="prism language-python">代码块在这里
</code>
-----
<hr/>
-----
<p>这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用</p>
-----
<code class="prism language-python">代码块在这里
</code>
-----
<p><img src="https://img-blog.csdnimg.cn/20210129182417155.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNzYyMTkx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"/></p>
-----
<p><a href="https://blog.csdn.net/qq_43762191?spm=1001.2101.3001.5343">超链接,顺着网线来打我啊!!!</a></p>
-----
<hr/>
-----
<p>别忘了下划线哦<code>这是行代码</code></p>
-----
新结果,不论是在重复方面,还是在文字解码方面,都要优于上面的结果,所以我重新做一次分析。
1、首先,如果判断出来是公式的话,切分之后去掉空的部分,取倒数第二个元素即可。
2、如果是引用的话,还是换这个标签:./blockquote
来抓取比较好,因为不排除出现单行引用,那就和
<blockquote>
<p>这里是引用</p>
</blockquote>
或者
<blockquote>
<p>这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用<br/> 这里是引用<br/> 这里还是引用</p>
</blockquote>
3、在获取图片链接的时候,要注意将前后剔除干净。
4、注意行代码的提取。
5、正则时,既要提取标签,也要提取出文字,需要注意存放的问题。
其他的也没有啥了
首先,标记以及正文部分都在这个标签之下://*[@id="mainBox"]/main/div[1]
标题在这里://*[@id="articleContentId"]
正文在这里://*[@id="content_views"]
文中标题所在位置:
//*[@id="content_views"]//h1
//*[@id="content_views"]//h2
//*[@id="content_views"]//h3
//*[@id="content_views"]//h4
//*[@id="content_views"]//h5
//*[@id="content_views"]//h6
段落文本所在位置://*[@id="content_views"]//p
黄色标标所在位置://*[@id="content_views"]//p/mark
公式------所在位置://*[@id="content_views"]//p/span/span/span/span[2]//span/span[2]
黑色加粗所在位置://*[@id="content_views"]//p/strong
斜体字—所在位置://*[@id="content_views"]//p/em
无序标签所在位置://*[@id="content_views"]//ul//li/text()
有序标签所在位置://*[@id="content_views"]//ol//li/text()
待办和无序是一样的,不管了,反正也只是用着好玩。
引用标签所在位置://*[@id="content_views"]//blockquote/p//text()
代码块儿所在位置://*[@id="content_views"]//pre/code/text()
行代码—所在位置://*[@id="content_views"]//p/code
超链接—所在位置://*[@id="content_views"]//p/a
表格表头所在位置://*[@id="content_views"]//div/table//th
表格内容所在位置://*[@id="content_views"]//div/table//td
下划线—所在位置://*[@id="content_views"]//hr
图片://*[@id="content_views"]//p/img
经过上面缜密的分析,我准备完整的爬取一篇博客并保存到正确的文件中。
爬哪篇呢?自然是测试文档了。
经过一会儿的努力,我写出了这样的正则表达式:
res = re.findall('(<.+?>)',string = string)
#res2 = re.findall('(>.*?<)',string = string)
res2 = re.findall('(>[\s\S]*?<)',string=string)
print(res)
# 因为在提取第二个正则表达式的时候,会带上‘>’和‘<’,所以需要剔除一下
for r2 in range(len(res2)):
res2[r2] = res2[r2].replace('>', '').replace('<', '').replace('\n', '').strip()
# 在遍历时修改需要使用下标
# 字符串一旦写完,就不能通过下标对其进行修改
for r3 in res2[:]: # 不用res2[:]的话,遍历会跳步
if r3 == '':
res2.remove(r3)
result = ''.join(res2) # 这里不应该简单整合,这个整合给公式就好了。
# 这段操作稍后会单独整理一份博客。
print(result)
首先,拿最简单的先试一下:
string = '一级标题
'
[''
, '', '']
一级标题
说明这个表达式初步可用了。
再拿长一点的:
[''
, '']
这是一篇测试文档,现在不知道干嘛用很正常,我在写一个爬虫的项目,等我爬虫自学系列最后一篇出来就知道啦,到时候如果你们想复现的话,直接来我这里拿就好。
应该可以看出来拿的是哪个啊。
接下来,就是我们前面看着就烦的公式部分了。
能否成功呢?
string = '''
a
=
b
+
c
a = b + c
a=b+c
'''
结果:
[''
, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
a=b+c
看得出来,很成功啊!!!
但是,等下的算法里面不能忘记对链接的处理,他们可都在标签里面呢!!!
前面的代码好像还有一点改动,记不得了。
有了这个状态机,就可以初步的把标签啥的都打上去了。
当然,还有需要改动的地方,只是目前我觉得性价比不高,就没写。
def get_div_name(div_list):
'''
这是一个用于提取标签的状态机
:param div_list: 标签列表
:return: 最终标签名
'''
if div_list[0] == '
':
return '【下划线】'
elif div_list[0][1] == 'h':
hn = re.search('[0-9]{1}',div_list[0]).group(0)
return '【' + hn + '级标题】'
elif div_list[0] == '' :
return '【枚举】'
elif div_list[0] == '' :
return '【待办】'
elif div_list[0] == ''
:
return '【引用】'
elif ' in div_list[0]:
l = re.search('(-.+?")',div_list[0])
language = l.group(0).replace('-','').replace('"','')
return '【'+language+'语言代码块儿】'
elif div_list[0] == ''
:
if div_list[1] == '':
return '【纯文本】'
elif 'katex' in div_list[1]:
return '【公式】'
elif div_list[1] == '':
return '【黄标突出】'
elif div_list[1] == '':
return '【加粗】'
elif div_list[1] == '':
return '【斜体】'
elif div_list[1] == ''
:
return '【行代码】'
elif 'img' in div_list[1]:
h = re.search('(".+?")',div_list[1])
href = h.group(0).replace('"','').replace('"','')
return '【图片】:' + href
elif 'href' in div_list[1]:
h = re.search('(".+?")', div_list[1])
href = h.group(0).replace('"', '').replace('"', '')
return '【超链接:】'+ href
else:
return ''
else:
return ''
def outdata(url):
try:
print('succeed'+url)
res = requests.get(url,headers=header)
code = res.apparent_encoding # 获取url对应的编码格式
res.encoding = code
wbdata = res.text
tree = etree.HTML(wbdata)
el_list = tree.xpath('//*[@id="content_views"]')
for el in el_list:
es = el.xpath('./h1 | ./h2 | ./h3 | ./h4 | ./h5 | ./h6 | ./p |./ul//li | ./ol//li | ./ul//li | ./blockquote | ./pre/code '
'| ./div/table/thead/tr//th | ./div/table/tbody/tr//td | ./hr')
for e in es:
string = etree.tostring(e,encoding=code).decode(code)
res = re.findall('(<.+?>)', string=string)
res2 = re.findall('(>[\s\S]*?<)', string=string)
div_name = get_div_name(res)
for r2 in range(len(res2)):
res2[r2] = res2[r2].replace('>', '').replace('<', '').replace('\n', '').strip()
for r3 in res2[:]: # 不用res2[:]的话,遍历会跳步
if r3 == '':
res2.remove(r3)
if div_name != '':
res2.insert(0,div_name)
print('\n'.join(res2))
print('-----')
except:
print('failed'+url)
接近尾声了啊。
又做了点微调,然后将数据保存到了文件里面。
def save_to_file(file_name,contant):
'''
这个函数用于将数据写入到文件中
:param file_name:文件名
:param contant: 文件内容
:return: none
'''
file_path = r'D:\CSDN博客'
if not os.path.exists(file_path): # 如果目标文件夹不存在
os.mkdir(file_path)
w_file_path = file_path+'\\'+'file_name'+'.txt'
f = open(w_file_path,'w')
for c in contant:
f.write(c)
f.close()
其实吧,也就是扔进线程池里去处理。
所以就在下面加两行线程池的启动即可:
url_list = pd.read_csv('My_CSDN.csv')['url']
Thread_Pool(outdata,datalist=url_list,Thread_num = 10)
简陋了点,但是1.0版本是出来了,接下来就是优化的事情了。
本文代码还算详尽,要拿完整代码,扫旁边二维码,后台回复:“博客”,获取当前最新版本。
2021.2.1好前会放上第一个版本。
至于私密博客,回头优化的时候会带上。
有感情是一回事儿,被欺骗是另一回事儿。被伤过的心还怎么再爱呢?