因为想看一下自己网站的关键词在百度搜索结果中的排名,所以,使用Python3编写了一个脚本进行爬取分析。
代码非常简单,水平一般(娇羞中),分享出来和大家交流。
先来说说实现过程吧!
一、百度搜索关键词的URL规则
例如:搜索本站域名“opython.com”。
搜索之后,点开搜索结果的第2页,URL类似:
https://www.baidu.com/s?wd=opython.com&pn=10&oq=opython.com&tn=baiduhome_pg&ie=utf-8&rsv_idx=2&rsv_pq=a99fa2280001734f&rsv_t=2b57Fux0BL2%2BdMJ6OjiZ6HP%2BE3OuYBUcSTarswtNJIr9qgkep2qSgUAMF0GOxB59Ja1o
为什么是第2页呢?
因为第1页看不到分页的参数。
这个URL是很长的一段内容,但实际上有用的就2个。
大家能够看到“https://www.baidu.com/s?”后面都是参数,真正有用的是“wd”和“pn”,也就是关键词和页码。
也许大家不太理解,为什么第2页的URL中参数“pn”的值是“10”。
这是因为搜索结果每一页的记录条数是10,这个数字代表当前页的搜索记录“id”是从10之后开始。
当我们了解了上述内容之后,就能够非常清楚的知道爬取某一个关键词搜索结果每一页的URL如何生成了。
格式:https://www.baidu.com/s?wd=[关键词]&pn=[页码减1乘10]
二、搜索结果的关键元素
百度的搜索结果页源代码中包含10条搜索记录。
但是,大家查看的时候,不要在浏览器中点鼠标右键选择查看源代码的选项,那样什么都看不到。
需要使用谷歌浏览器(Chrome)的检查或者火狐浏览器(Firefox)的审查元素功能才可以。
每一条搜索结果记录的内容类似:
<div class="result c-container " id="1" srcid="1599" tpl="se_com_default" data-click="{"rsv_bdr":"0","p5":1}"> ...省略部分代码... <a target="_blank" href="http://www.baidu.com/link?url=Ie_6NlHRiBISswMoiY1_LveUBXlni_TXQcf45SJmKjK" class="c-showurl" style="text-decoration:none;"><b>opython.com</b>/ </a> ...省略部分代码... 百度快照</a></div></div>
代码太长,省略了无用的内容。
在保留的关键内容中,大家注意红字部分。
每条搜索结果记录的“id”是页面中唯一的,我们可以通过“id”来检查每条搜索记录是否包含查询的域名(关键词)。
但是,“id”是搜索记录最外层“<div>”标签的属性,我们还需要找到内部包含真实URL的“<a>”标签,获取它包含的字符串进行匹配。
这个包含真实URL的“<a>”标签,有一个“class”属性“c-showurl”,通过这个属性我们就能够比较准确的找到这个标签,并获取它内部包含的字符串。
不过要注意的是,获取到的字符串中会包含“<b>”标签,需要先清除掉。
因为有时被加粗的只是真实URL的一部分,导致我们不能获取到真实的地址。
例如,搜索“Python”这个关键词的时候,如果搜索结果记录中有“www.opython.com”这个地址,就会以“www.o<b>python</b>.com”的形式呈现。
三、模拟浏览器打开URL的行为
如果直接使用urllib库中request模块的urlopen()方法打开需要爬取的百度搜索页,是不能正常获取页面内容的。
得到的结果类似:
<html> <head> <script> location.replace(location.href.replace("https://","http://")); </script> </head> <body> <noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript> </body> </html>
为了能够正常的获取搜索结果内容,我们需要模拟浏览器打开网页的行为。
这里使用request模块中的build_opener()方法,创建一个打开URL的工具对象,
示例代码:
opener = request.build_opener()
然后,为这个对象添加“header”信息。
示例代码:
opener.addheaders = [('User-agent', # 添加模拟浏览器访问的header信息 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11')]
“header”信息中的“User-Agent”是用于告诉远程服务器,客户端的软件环境。
最后,通过对象的open()方法打开URL获取页面内容。
示例代码:
html = self.opener.open(url).read().decode()
四、使用BeautifulSoup解析HTML
获取到的HTML内容,我们需要从中取得想要的信息。
BeautifulSoup是一个非常好用的用于解析HTML和XML的第三方库。
安装命令:
pip install beautifulsoup4
注意:使用这个库是“from bs4 import XXX”。
使用方法:
1、创建格式化后的文档对象
示例代码:
soup = BeautifulSoup(html, 'lxml')
注意:第二个参数“’lxml’”表示解析的是HTML。
2、通过文档对象进行查询操作
示例代码:
soup.p # 获取文档对象中第1个<p>标签的内容 soup.p.string # 获取文档中第1个<p>标签包含的字符串 soup.find(id=1) # 获取文档对象中第1个“id”属性为“1”的标签 soup.find('a', 'c-showurl') # 获取文档对象中“class”属性为“c-showurl”的<a>标签
更多使用帮助,请查看官方中文文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
五、最终实现
有了以上内容的支撑,我们就可以动手编写全部代码了。
示例代码:
from urllib import request from urllib import parse from bs4 import BeautifulSoup # 用于解析HTML import multiprocessing # 用于多进程 class BaiduRanking: # 定义爬取排名的类 def __init__(self, words, page_number, domain): self.words = words # 查询的关键词列表 self.page_number = page_number # 查询的页面数量 self.domain = domain # 查询的域名 self.opener = request.build_opener() # 创建打开URL的工具对象 self.opener.addheaders = [('User-agent', # 添加模拟浏览器访问的header信息 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11')] self.position = [] # 域名在搜索引擎查询结果中出现的位置列表 def get_urls(self, word): # 定义每个关键词查询页面的URL列表生成器 urls = [] for pn in range(self.page_number): url = 'https://www.baidu.com/s?wd=%s&pn=%s' % (parse.quote_plus(word), pn * 10) # 注意关键词转换格式 urls.append(url) yield urls def get_result(self, url, position, current_page): # 定义获取排名结果的方法 print(url) try: html = self.opener.open(url).read().decode().replace('<b>', '').replace('</b>', '') # 获取页面内容并替换掉字体加粗标签 soup = BeautifulSoup(html, 'lxml') # 解析HTML for i in range(current_page * 10 + 1, current_page * 10 + 11): # 轮询查询结果的id try: if self.domain in soup.find(id=i).find('a', 'c-showurl').string: # 判断从查询结果中找到的网址字符串是否包含域名 position.append(i) # 添加查询结果id到位置列表 except: # 发生异常时继续查询 continue except: pass # 某页面发生异常时忽略 def run_task(self): # 定义运行爬取任务的方法 for word in self.words: # 遍历关键词列表 position = multiprocessing.Manager().list() # 创建多进程共享的列表变量 current_page = 0 # 当前查询的页面编号 print('正在查询关键词:%s' % word) for urls in self.get_urls(word): # 遍历当前关键词需要爬取的所有URL processes = multiprocessing.Pool(5) # 开启进程池 for url in urls: # 遍历当前关键词的所有URL processes.apply_async(self.get_result, args=(url, position, current_page)) # 为每个URL爬取任务创建进程 current_page += 1 # 页面编号自增 processes.close() # 关闭进程池 processes.join() # 等待进程结束 print('查询完毕。\n出现位置:%s' % sorted(position)) # 显示输出排序后的爬取结果 if __name__ == '__main__': domain = 'opython.com' # 查询的域名 words = ['Django2教程', 'Python新手教程'] # 查询的关键词列表 page_number = 10 # 每个关键词爬取的页面数量 ranking = BaiduRanking(words=words, domain=domain, page_number=page_number) # 实例化爬取排名的对象 ranking.run_task() # 运行爬取任务