寻找真正问题的爬虫

0x00。原因

因为要参加大学生的创新大赛,研究微博的博文所表达的情感,需要大量的微博的博文,而且无论是国内的某个学位,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。爬行动物不能用很正常。不要气馁。爬虫程序和反爬行机制一直在不断博弈。

另外,转载请告知博主,如果你认为博是老板就不需要告知。