评论文本爬虫
因为要参加大学生的创新大赛,研究微博的博文所表达的情感,需要大量的微博的博文,而且无论是国内的某个学位,csdn,还是国外的Google,gayhub,codeproject,都找不到想要的程序,只好自己写程序了。
赞美诗我在《攀登联盟》里找到一个类似的程序,不过是在windows下,源码是关闭的。而且爬取保存的文件,用notepad++打开,出现了很多奇怪的问题,我就放弃了。
0x001。基础知识
这个程序是用python写的,所以基本的Python知识是必须的。另外,如果你有一定的计算机网络基础,在前期准备的时候会少走很多弯路。
对于爬行动物,你需要明确以下几点:
1.爬取对象的分类可以分为以下几类:第一类是不需要登录的,比如博主以前练手的时候爬的中国天气网。这类网页很难抓取,建议爬虫新手爬这类网页;二是登录,如豆瓣、新浪微博,难爬;第三种独立于前两种,你想要的信息一般是动态刷新的,比如AJAX或者嵌入式资源。这种爬虫是最难的,博主也没研究过,这里就不细说了(据同学说,淘宝的商品评论就属于这一类)。
2.如果同一个数据源有多种形式(如电脑版、手机版、客户端等。),更“纯粹”的呈现方式更受青睐。比如新浪微博有网页版和手机版,手机版可以通过电脑浏览器访问。这个时候,我更喜欢手机版的新浪微博。
3.爬虫一般是把网页下载到本地,然后通过某种方式提取感兴趣的信息。换句话说,抓取网页只是完成了一半,你需要从下载的html文件中提取你感兴趣的信息。这时候就需要一些xml的知识了。在这个项目中,博客作者使用XPath提取信息,也可以使用XQuery等其他技术。详情请访问w3cschool。
4.爬虫要尽可能模仿人类。现在网站的反抓取机制已经开发出来了。从验证码到IP禁止,爬虫技术和反爬虫技术可谓是连续博弈。
0x02。去
确定爬虫的目标后,首先要访问目标网页,了解目标网页属于以上哪种爬虫。另外,记录下你需要采取的步骤,才能得到你感兴趣的信息,比如是否需要登录,如果需要登录,是否需要验证码;你需要做什么才能得到你想要的信息,是否需要提交一些表格;你想要的信息所在页面的url有什么规则等等。
以下博文以blogger项目为例。该项目抓取特定新浪微博用户自注册以来的所有微博博文,按关键词抓取100页微博博文(约1000篇)。
0x03。收集必要的信息
首先访问目标网页,发现需要登录。进入登录页面如下:新浪微博手机版登录页面。
注意,在url的后半部分有许多像“%xx”这样的转义字符,这将在本文后面讨论。
从这个页面可以看到,登录新浪微博手机版需要填写账号、密码和验证码。
这个验证码只需要最近提供(本文创建于2016.3.11)。如果不需要提供验证码,会有两种登录方式。
第一种方法是进行js模拟,填写账号密码后点击“登录”按钮。博主之前用这个方法写了一个Java爬虫,现在找不到项目了,这里就不赘述了。
第二种需要一定的HTTP基础,提交包含所需信息的HTTP POST请求。我们需要Wireshark工具来捕获我们在登录微博时发送和接收的数据包。如下图所示,我抓取了登录时收发的数据包。Wireshark抢到了1的结果。
在搜索栏中提供搜索条件"/(displayID)?page=(pagenum)" .这将是我们的爬虫拼接url的基础。
接下来看网页的源代码,找到我们想要的信息的位置。打开浏览器开发者工具,直接定位一个微博,就可以找到它的位置,如下图。
xpath
观察html代码,发现所有微博都在< div & gt标签,这个标签中有两个属性,其中class属性是“c”和一个唯一的id属性值。获取这些信息有助于提取所需的信息。
此外,还有一些因素需要特别注意。
*微博分为原创微博和转发微博。
*根据发布时间与当前时间的不同,页面上显示时间的方式有多种,如“MM分钟前”、“今天的HH:MM”、“MM月dd日HH:MM-DD hh: mm: SS”。*手机版新浪微博一页显示约10条微博,注意总量* *。
0x04。编码
1.抓取用户微博
这个项目的开发语言是Python 2.7,项目中使用了一些第三方库,可以通过pip添加。
由于验证码阻挡了自动登录的思路,用户要访问特定用户的微博页面,只能提供cookies。
第一个是Python的请求模块,它提供带有cookies的url请求。
导入请求
打印请求。get (url,cookies = cookies)。内容使用此代码打印带有cookies的URL请求页面结果。
首先,获取用户的微博页面数量。通过检查网页的源代码,找到代表页数的元素,通过XPath等技术提取页数。
页数
该项目使用lxml模块通过XPath提取html。
首先,导入lxml模块,项目中只使用etree,所以从lxml导入etree。
然后用下面的方法返回页码。
def getpagenum(self):
URL = self . geturl(pagenum = 1)
html = requests.get(url,cookies=self.cook)。内容#访问第一页获取页码。
选择器= etree。HTML(html)
pagenum = selector . XPath('//input[@ name = " MP "]/@ value ')[0]
return int(pagenum)
下一步是连续拼接网址->访问网址-& gt;下载网页。
需要注意的是,由于新浪反爬取机制的存在,如果同一个cookies访问页面过于频繁,就会进入类似的“冷静期”,即会返回一个无用的页面。通过分析这个无用的页面,发现这个页面在特定的地方会有特定的信息,这个页面对我们是否有用可以通过XPath技术来判断。
def ispageneeded(html):
选择器= etree。HTML(html)
尝试:
title = selector . XPath('//title ')[0]
除了:
返回False
返回title.text!= '微博广场'和title.text!= '微博'
如果有无用的页面,你只需要再次访问它们。但是通过后来的实验发现,如果长时间频繁访问它们,那么返回的页面都是无用的,程序会陷入死循环。为了防止程序陷入死循环,博主设置了一个trycount阈值,超过阈值后方法会自动返回。
下面的代码片段展示了单线程爬虫的方法。
def startcrawling(self,startpage=1,trycount=20):
尝试= 0
尝试:
OS . mkdir(sys . path[0]+'/Weibo _ raw/'+self . wanted)除了例外,e:
打印字符串(e)
isdone = False
while not isdone并尝试& lt尝试计数:
尝试:
pagenum = self.getpagenum()
isdone = True
除了例外,e:
尝试+= 1
if attempt == trycount:
返回False
i =起始页
而我& lt= pagenum:
尝试= 0
isneeded = False
html = ' '
while not isneeded and attempt & lt尝试计数:
html = self . getpage(self . geturl(I))
isneeded = self . ispageneeded(html)
如果不需要:
尝试+= 1
if attempt == trycount:
返回False
self . save html(sys . path[0]+'/Weibo _ raw/'+self . wanted+'/'+str(I)+'。txt ',html)打印字符串(i) + '/' +字符串(pagenum - 1)
i += 1
返回True
考虑到程序的时间效率,在写了单线程爬虫之后,博主又写了多线程爬虫版本。基本思路是微博页面数除以跟帖数。比如微博中的一个用户有100个微博页面,程序有10个线程,那么每个线程只负责爬取10个页面。其他基本思路和单线程差不多,只有边界值需要小心处理,这里就不赘述了。另外,由于多线程的效率比较高,并发量特别大,服务器很容易返回无效页面,所以trycount的设置比较重要。博主在写这条微博的时候,用了一个新的cookie来测试谁爬了北京邮电大学的微博。3976条微博文章全部成功爬取,博文提取。只用了15s,这其实可能和新旧cookies以及网络环境有关。命令行设置如下,项目网站中解释了命令行的含义:python main.py _ T _ WM = xxxSUHB = xxxSUB = xxxGSID _ CTANDWM = XXX UBUPPT M 20 20以上爬行工作的基本介绍结束,接下来分析爬虫的第二部分。因为项目提供了多线程抓取的方法,而多线程一般是乱序的,但是微博的博文是按时间排序的,所以项目采用了折中的方法,将下载的页面保存在本地文件系统中,每个页面以其页码作为文件名。爬行工作完成后,遍历并解析文件夹中的所有文件。
通过前面的观察,我们了解到了微博的博文都有哪些特点。通过使用XPath技术,从这个页面中提取所有具有这个特性的标签并不困难。
再次,微博分为转发微博和原创微博,时间表达。另外,因为我们的研究课题只对微博文字感兴趣,所以不考虑插图。
def startparsing(self,parsing time = datetime . datetime . now()):
basepath = sys . path[0]+'/Weibo _ raw/'+self . uid for filename in OS . listdir(basepath):
if filename.startswith(' . '):
继续
path = basepath + '/' + filename
f =打开(路径,“r”)
html = f.read()
选择器= etree。HTML(html)
weiboitems = selector . XPath('//div[@ class = " c "][@ id]')用于Weibo items中的项目:
微博=微博()
weibo.id = item.xpath('。/@id')[0]
cmt = item.xpath('。/div/span[@ class = " CMT "]')if len(CMT)!= 0:
weibo.isrepost = True
weibo.content = cmt[0]。文本
否则:
weibo.isrepost = False
ctt = item.xpath('。/div/span[@class="ctt"]')[0]
如果ctt.text不为None:
weibo.content += ctt.text
对于ctt.xpath中的。/a '):
如果a.text不是None:
微博.内容+= a.text
如果a.tail不为None:
weibo.content += a.tail
if len(cmt)!= 0:
reason = CMT[1]. text . split(u ' \ xa0 ')
if len(原因)!= 1:
Weibo . reportstroy = reason[0]
ct = item.xpath('。/div/span[@class="ct"]')[0]
time = ct.text.split(u'\xa0')[0]
weibo.time = self.gettime(self,time,parsingtime)self.weibos.append(微博。__字典_ _)
f.close()
方法传递的参数parsingtime的设置初衷是开发初期抓取和解析可能不会同时进行(并非严格意义上的“同时”),微博时间显示以访问时间为准,比如抓取时间为10:00,五分钟前发布了一条微博显示,但如果解析时间为10:30,解析时间就会出错,所以,到爬虫基本发育结束,爬行和解析的开始时间差距会缩小,时间差就是爬行过程的时间,基本可以忽略。
解析结果保存在列表中。最后,列表以json格式保存到文件系统,并删除转换文件夹。
定义保存(自己):
f = open(sys . path[0]+'/Weibo _ parsed/'+self . uid+'。txt ',' w ')JSON str = JSON . dumps(self . weibos,indent=4,确保_ascii=False)f.write(jsonstr)
f.close()
抓取关键词
同样,收集必要的信息。在微博手机搜索页面输入“python”,观察网址,研究其规律。虽然第一页没有规则,但是我们在第二页发现了一个规则,这个规则可以应用回第一页。
第二页
申请后的第一页
观察url可以发现,url中唯一的变量是关键字和页面(其实hideSearchFrame对我们的搜索结果和爬虫没有影响),所以我们可以在代码中控制这两个变量。
另外,如果关键词是中文,那么网址需要转换汉字。例如,如果我们在搜索框中键入“Happy”进行搜索,我们发现url显示Happy Search如下。
但是它被复制为
/search/mblog?hideSearchFrame = & amp关键字= % E5 % BC % 80 % E5 % BF % 83 & ampPage=1好在python的urllib库有qoute方法处理中文转换的功能(如果是英文就不转换了),所以在拼接URL之前用这个方法处理参数。
另外,考虑到关键词搜索属于数据收集阶段使用的方法,我们这里只提供网页的单线程下载。如果有多线程的需求,可以按照多线程抓取用户微博的方法自己重写。最后提取并保存下载的网页(我知道这个模块设计有点奇怪,所以打算重新创建的时候再改(郝),就这样吧)。
def关键字爬网(self,keyword):
real keyword = URL lib . quote(keyword)#用中文处理关键字。
尝试:
OS . mkdir(sys . path[0]+'/keywords ')
除了例外,e:
打印字符串(e)
微博= []
尝试:
high points = re . compile(u '[\ u 00010000-\ u 0010 ffff]')#处理表情符号,但是好像不管用。
除了re.error:
high points = re . compile(u '[\ ud 800-\ uDBFF][\ UDC 00-\ uDFFF]')pagenum = 0
isneeded = False
当不需要时:
html = self . get page('/search/mblog?关键字= % s & amppage = 1 ' % real keyword)is needed = self . ispageneeded(html)
如果需要:
选择器= etree。HTML(html)
尝试:
pagenum = int(selector . XPath('//input[@ name = " MP "]/@ value ')[0])除了:
pagenum = 1
对于范围内的I(1,pagenum + 1):
尝试:
isneeded = False
当不需要时:
html = self . get page('/search/mblog?关键字= % s & amppage=%s' % (realkeyword,str(I)))is needed = self . ispageneeded(html)
选择器= etree。HTML(html)
weiboitems = selector . XPath('//div[@ class = " c "][@ id]')用于Weibo items中的项目:
cmt = item.xpath('。/div/span[@ class = " CMT "]')if(len(CMT))= = 0:
ctt = item.xpath('。/div/span[@class="ctt"]')[0]
如果ctt.text不为None:
text = etree.tostring(ctt,method='text ',encoding = " unicode ")tail = CTT . tail
if text.endswith(tail):
index = -len(tail)
text = text[1:index]
text = highpoints.sub(u'\u25FD ',text) #表情符号的处理方式,似乎行不通。
微博文本=文本
微博附加(微博文本)
打印字符串(i) + '/' +字符串(pagenum)
除了例外,e:
打印字符串(e)
f = open(sys . path[0]+'/keywords/'+keyword+'。txt ',' w ')尝试:
f.write(json.dumps(weibos,indent=4,确保_ ascii = False))Exception除外,例如:
打印字符串(ex)
最后:
f.close()
博客作者以前从来没有写过任何爬虫程序。为了获取新浪微博博文,博主们编写了三种不同的爬虫程序,包括Python和Java。爬行动物不能用很正常。不要气馁。爬虫程序和反爬行机制一直在不断博弈。
另外,转载请告知博主,如果你认为博是老板就不需要告知。