BeautifulSoup 根据输入的公司名称来爬取公司的详细信息----2022-08-24更新版
- 因为有不少博友私信我除了工商信息板块,其他板块的信息能不能也处理一下,所以我抽空新增了一下功能,对于爬取的整体页面,进行更多的信息处理。
- 在此强烈声明,本笔记仅用于个人学习用途,提供一些关于BeautifulSoup的用法和数据预处理的思路,并不支持大量数据的爬取,如果大家想爬取数据量大的公司群体,建议自学一下代理池和并行运行等方面的知识。
1、获取headers
1、进入qcc官网进行注册并登录。
2、然后按F12弹出开发者工具,点击Network,然后你会看到企查查这个网址,点击一下
然后可以找到我们需要复制的header(User-Agent与cookie),这是非常关键的步骤,切记这个header(User-Agent与cookie)是自己注册之后登录成功所获取的header(User-Agent与cookie),这样方便后面保存一次之后就可以在一定时间内无限访问网址进行查询的操作。
from bs4 import BeautifulSoup
import requests
import time
import pandas as pd
from requests.auth import HTTPBasicAuth
import os
import xlwings as xw
import re
# 保持会话
# 新建一个session对象
sess = requests.session()
# 添加headers(header为自己登录的企查查网址,输入账号密码登录之后所显示的header,此代码的上方介绍了获取方法)
afterLogin_headers = {'User-Agent': '此代码上方介绍了获取的方法','cookie':'此代码上方介绍了获取的方法'}
# 以用户的身份认证,方便后面执行查询指令
from requests.auth import HTTPBasicAuth
res = requests.get('https://www.qcc.com/',headers=afterLogin_headers, auth=HTTPBasicAuth('qcc登录成功后的用户名', '登录成功后的qcc密码'))
print(res.status_code)
整段代码的含义为:伪装成用户进行登录行为(返回200状态码代表着登录成功)。有些人爬不到信息,很可能是自己的账号密码不对,或者自己复制的User-Agent以及cookie不对,如果不知道自己写的对不对,执行一下下面这个代码则可证明:
# 测试所用,在上面那一段代码执行完后,为了验证自己有无登录企查查成功,可随意爬取某个网页并显示是否有正确对应的内容。
# 获取查询到的网页内容(全部)
search = sess.get('https://www.qcc.com/web/search?key={}'.format('腾讯'),headers=afterLogin_headers, auth=HTTPBasicAuth('企查查登录成功后的用户名', '登录成功后的企查查密码'), timeout=10)
search.raise_for_status()
search.encoding = 'utf-8' #linux utf-8
soup = BeautifulSoup(search.text,features="html.parser")
print(soup)
2、登录成功后,可根据输入的公司名称进行查询操作,得到所需要的内容。
# 获取特定公司的指定信息
def get_company_message(company, afterLogin_headers):
# 获取查询到的网页内容(全部)
search = sess.get('https://www.qcc.com/web/search?key={}'.format(company),headers=afterLogin_headers,timeout=10)
search.raise_for_status()
search.encoding = 'utf-8' #linux utf-8
soup = BeautifulSoup(search.text,features="html.parser")
href = soup.find_all('a',{'class': 'title'})[0].get('href')
target_company = soup.find_all('a',{'class': 'title'})[0].text
time.sleep(4)
details = sess.get(href,headers=afterLogin_headers,timeout=10)
details.raise_for_status()
details.encoding = 'utf-8' #linux utf-8
details_soup = BeautifulSoup(details.text,features="html.parser")
# 获取企业主页
conpany_homePage = details_soup.find_all('div',{'class': 'content'})[0]
# 获取工商信息
business_message = details_soup.find_all({'table': 'ntable'})[0].text
# 获取股东信息
try:
partner_message = details_soup.find_all(id=re.compile('partner'))[0].select('table')
# 如果列表为空,则代表改信息只有vip账号才能获取,设置为0跳过处理
if partner_message==[]:
partner_message = 0
except:
partner_message = 0
# 获取主要人员
try:
main_people = details_soup.find_all(id='mainmember')[0].select('table')
# 如果列表为空,则代表改信息只有vip账号才能获取,设置为0跳过处理
if main_people==[]:
main_people = 0
except:
main_people = 0
# 获取对外投资
try:
external_touzi = details_soup.find_all(id=re.compile('touzilist'))[0].select('table')
# 如果列表为空,则代表改信息只有vip账号才能获取,设置为0跳过处理
if external_touzi==[]:
external_touzi = 0
except:
external_touzi = 0
# 获取变更记录
try:
change_record = details_soup.find_all(id=re.compile('changelist'))[0].select('table')
if change_record == []:
change_record = 0
except:
change_record = 0
return [target_company,conpany_homePage, business_message, partner_message, main_people, external_touzi, change_record]
上面的函数get_company_message(),函数如其名,是为了获取公司的文本信息。总体上包括着三个步骤。
- ①查询某公司
- ②点击进入第一位搜索结果的新网站
- ③获取该搜索结果中关于企业主页、工商信息、股东信息、主要人员、对外投资、变更记录等这些卡片的网页信息,其他卡片的网页信息据我观察大部分都需要vip,我没有vip账号,所以不对这些板块进行信息处理
3、将获取到的表格内容进行文本特殊化处理,并将其汇总成一个dataframe,方便后面保存为csv
3.1、将企业主页头部的信息转化为dataframe
# 获取企业主页头部的信息
def conpany_homePage_to_df(company_name,company_message):
if company_message==0:
homePage_df = pd.DataFrame()
else:
company_content = company_message.find_all('div', {'class': 'contact-info'})[0].text.replace("\n", "").replace(" ", "")
# 法定代表人
try:
c1 = company_content.split("法定代表人:")[1].split('关联')[0]
except:
c1 = '无'
# 统一社会信用代码
try:
c2 = company_content.split("统一社会信用代码:")[1].split("复制")[0]
except:
c2 = '无'
# 电话
c3 = company_content.split("电话:")[1].split("同电话")[0].split("更多")[0]
# 官网
c4 = company_content.split("官网:")[1].split("邮箱")[0]
# 邮箱
c5 = company_content.split("邮箱:")[1].split("复制")[0]
# 地址
c6 = company_content.split("地址:")[1].split("附近企业")[0]
# 简介
c7 = company_content.split("简介:")[1].split("复制")[0]
homePage_df = pd.DataFrame({'公司名称': [company_name],'法定代表人': [c1],'统一社会信用代码': [c2],\
'电话': [c3], '官网': [c4], '邮箱': [c5], '地址': [c6], '简介': [c7]})
return homePage_df
3.2、将工商信息版块的信息转化为dataframe
import pandas as pd
# 获取工商信息的卡片
def business_message_to_df(message):
# 统一社会信用代码
unified_social_credit_code = []
try:
unified_social_credit_code.append(message.split('统一社会信用代码')[1].split('复制')[0].replace(" ","").replace("\n",""))
except:
unified_social_credit_code.append('无法收集')
# 企业名称
list_companys = []
try:
list_companys.append(message.split('企业名称')[1].split('复制')[0].replace(" ","").replace("\n",""))
except:
list_companys.append('无法收集')
# 法定代表人
Legal_Person = []
try:
Legal_Person.append(message.split('法定代表人')[1].split('关联')[0].replace("\n","").replace(" ",""))
except:
Legal_Person.append('无法收集')
# 登记状态
Registration_status = []
try:
Registration_status.append(message.split('登记状态')[1].split('成立日期')[0].replace(" ","").replace("\n",""))
except:
Registration_status.append('无法收集')
# 成立日期
Date_of_Establishment = []
try:
Date_of_Establishment.append(message.split('成立日期')[1].split('注册资本')[0].replace(" ","").replace("\n",""))
except:
Date_of_Establishment.append('无法收集')
# 注册资本
registered_capital = []
try:
registered_capital.append(message.split('注册资本')[1].split('实缴资本')[0].replace(' ','').replace("\n",""))
except:
registered_capital.append('无法收集')
# 实缴资本
contributed_capital = []
try:
contributed_capital.append(message.split('实缴资本')[1].split('核准日期')[0].replace(' ','').replace('\n',''))
except:
contributed_capital.append('无法收集')
# 核准日期
Approved_date = []
try:
Approved_date.append(message.split('核准日期')[1].split('组织机构代码')[0].replace(' ','').replace("\n",""))
except:
Approved_date.append('无法收集')
# 组织机构代码
Organization_Code = []
try:
Organization_Code.append(message.split('组织机构代码')[1].split('复制')[0].replace(' ','').replace("\n",""))
except:
Organization_Code.append('无法收集')
# 工商注册号
companyNo = []
try:
companyNo.append(message.split('工商注册号')[1].split('复制')[0].replace(' ','').replace("\n",""))
except:
companyNo.append('无法收集')
# 纳税人识别号
Taxpayer_Identification_Number = []
try:
Taxpayer_Identification_Number.append(message.split('纳税人识别号')[1].split('复制')[0].replace(' ','').replace("\n",""))
except:
Taxpayer_Identification_Number.append('无法收集')
# 企业类型
enterprise_type = []
try:
enterprise_type.append(message.split('企业类型')[1].split('营业期限')[0].replace('\n','').replace(' ',''))
except:
enterprise_type.append('无法收集')
# 营业期限
Business_Term = []
try:
Business_Term.append(message.split('营业期限')[1].split('纳税人资质')[0].replace('\n','').replace(' ',''))
except:
Business_Term.append('无法收集')
# 纳税人资质
Taxpayer_aptitude = []
try:
Taxpayer_aptitude.append(message.split('纳税人资质')[1].split('所属行业')[0].replace(' ','').replace("\n",""))
except:
Taxpayer_aptitude.append('无法收集')
# 所属行业
sub_Industry = []
try:
sub_Industry.append(message.split('所属行业')[1].split('所属地区')[0].replace('\n','').replace(' ',''))
except:
sub_Industry.append('无法收集')
# 所属地区
sub_area = []
try:
sub_area.append(message.split('所属地区')[1].split('登记机关')[0].replace(' ','').replace("\n",""))
except:
sub_area.append('无法收集')
# 登记机关
Registration_Authority = []
try:
Registration_Authority.append(message.split('登记机关')[1].split('人员规模')[0].replace(' ','').replace("\n",""))
except:
Registration_Authority.append('无法收集')
# 人员规模
staff_size = []
try:
staff_size.append(message.split('人员规模')[1].split('参保人数')[0].replace(' ','').replace('\n',''))
except:
staff_size.append('无法收集')
# 参保人数
Number_of_participants = []
try:
Number_of_participants.append(message.split('参保人数')[1].split('趋势图')[0].replace(' ','').replace("\n",""))
except:
Number_of_participants.append('无法收集')
# 曾用名
Used_Name = []
try:
Used_Name.append(message.split('曾用名')[1].split('英文名')[0].replace(' ','').replace("\n",""))
except:
Used_Name.append('无法收集')
# 英文名
English_name = []
try:
English_name.append(message.split('英文名')[1].split('进出口企业代码')[0].replace('\n','').replace(' ',''))
except:
English_name.append('无法收集')
# 进出口企业代码
import_and_export_code = []
try:
import_and_export_code.append(message.split('进出口企业代码')[1].split('复制')[0].replace(' ','').replace("\n",""))
except:
import_and_export_code.append('无法收集')
# 注册地址
register_adress = []
try:
register_adress.append(message.split('注册地址')[1].split('附近企业')[0].replace(' ','').replace("\n",""))
except:
register_adress.append('无法收集')
# 经营范围
Business_Scope = []
try:
Business_Scope.append(message.split('经营范围')[1].replace(' ','').replace("\n",""))
except:
Business_Scope.append('无法收集')
df = pd.DataFrame({'统一社会信用代码': unified_social_credit_code,\
'企业名称': list_companys,\
'法定代表人':Legal_Person,\
'登记状态':Registration_status,\
'成立日期':Date_of_Establishment,\
'注册资本':registered_capital,\
'实缴资本':contributed_capital,\
'核准日期':Approved_date,\
'组织机构代码':Organization_Code,\
'工商注册号':companyNo,\
'纳税人识别号':Taxpayer_Identification_Number,\
'企业类型':enterprise_type,\
'营业期限':Business_Term,\
'纳税人资质':Taxpayer_aptitude,
'所属行业':sub_Industry,\
'所属地区':sub_area,\
'登记机关':Registration_Authority,\
'人员规模':staff_size,\
'参保人数':Number_of_participants,\
'曾用名': Used_Name, \
'英文名':English_name, \
'进出口企业代码': import_and_export_code, \
'注册地址':register_adress,\
'经营范围':Business_Scope})
return df
这段代码是对爬取到的表格的文本内容进行文本识别处理,只适合于表格格式为2中第二张图片这样的,如果出现 “无法收集” 这个值,很有可能是因为该值不存在于爬取的表格文本中。
3.3、将除了上面两个之外其他版本的网页信息转化为dataframe
# 获取列名为横向的卡片信息
def col_is_vertical_to_df(company_name,message):
if message==0:
col_df = pd.DataFrame()
else:
# 定位到你要的那个表格,靠id=特定id,然后再在这个基础上找到table标签
list_col = []
list_row_all = []
# 获取列名
col_name = message[0].select('tr')[0].select('th')
for i in range(len(col_name)):
list_col.append(col_name[i].text.replace(' ','').replace('\n',''))
# 获取每一行的信息
row = len(message[0].select('tr'))-1
for i in range(row):
list_row = []
col_i = message[0].select('tr')[i+1].select('td')
for i in range(len(col_i)):
list_row.append(col_i[i].text.replace(' ','').replace('\n','').split('关联')[0])
list_row_all.append(list_row)
# 汇总为dataframe
col_df = pd.DataFrame(list_row_all, columns=list_col)
col_df['公司名称'] = company_name
return col_df
因为其他板块的信息基本都是这种格式:
所以可以用同一个函数处理并汇总成dataframe
4、为了便于阅读,我写了三个关于xlsx文件的处理函数
4.1、对于规则的列进行无列名合并
# 对于有规则的固定列,授予专门的处理函数
def regular_line(app, sheet_name, df):
if len(df) == 0:
pass
else:
# 给对应工作表添加内容
sheet = app.sheets[sheet_name]
# 获取工作表目前的行
if sheet.used_range.rows.count == 1:
now_row = sheet.used_range.rows.count
# 将dataframe的数据写进xlsx
sheet.range('A' + str(now_row)).options(pd.DataFrame, expand='table', index=False).value = df
sheet.range('A1').expand('right').api.Font.Bold = True
else:
now_row = sheet.used_range.rows.count + 1
# 将dataframe的数据写进xlsx
sheet.range('A' + str(now_row)).options(pd.DataFrame, expand='table',header=False,index=False).value = df
4.2、对于不规则的列进行列名合并
# 对于无规则的固定列,授予专门的处理函数
def Irregular_line(app, sheet_name, df):
if len(df) == 0:
pass
else:
# 给对应工作表添加内容
sheet = app.sheets[sheet_name]
# 获取工作表目前的行
if sheet.used_range.rows.count == 1:
now_row = sheet.used_range.rows.count
else:
now_row = sheet.used_range.rows.count + 2
# 将dataframe的数据写进xlsx
sheet.range('A' + str(now_row)).options(pd.DataFrame, expand='table', index=False).value = df
# 将列名那一行上黄色,然后数据行上灰色
for i in range(len(df2)+1):
if i == 0:
sheet.range('A' + str(now_row)).expand('right').color = (255,255,0)
else:
sheet.range('A' + str(now_row+i)).expand('right').api.Font.Color = 0x00000
sheet.range('A' + str(now_row+i)).expand('right').color = (220,220,220)
4.3、对于因为图片带有姓的原因造成姓重复的情况,进行去重
# 对于因为图片带有姓的原因造成姓重复的情况,进行去重,此函数只适合于大部分情况
def dedup_name(origin_name):
if len(origin_name)>=2:
if(origin_name[0:1] == origin_name[1:2]):
target_name = origin_name[1:]
else:
target_name = origin_name
target_name = target_name.replace('最终受益人','').replace('实际控制人','').replace('大股东','').replace('有限制','').replace('高消费','')
return target_name
5、输入公司名称
- 这里只是写个案例,所以随便写了个列表,一般跑自己代码的是读取自己的csv文件关于公司名称的那一列,然后转为列表)
# 测试所用
companys = ['深圳市腾讯计算机系统有限公司','阿里巴巴(中国)有限公司']
# 实际所用
# df_companys = pd.read_csv('自己目录的绝对路径/某某.csv')
# companys = df_companys['公司名称'].tolist()
5、最后执行此代码,查询companys列表中所有公司名称的详细信息并保存为csv。
save_path = '自己目录的绝对路径/某某.csv'
# 获取列表中的每一个公司的信息
app = xw.App(visible=False)
try:
for i in range(len(companys)):
# 获取该公司的部分网页信息
company_message = get_company_message(companys[i],afterLogin_headers)
# 企业主页
df1 = conpany_homePage_to_df(company_message[0],company_message[1])
# 工商信息
df2 = business_message_to_df(company_message[2])
df2['法定代表人'] = df2['法定代表人'].apply(dedup_name)
# 股东信息
df3 = col_is_vertical_to_df(company_message[0],company_message[3])
# 主要人员
df4 = col_is_vertical_to_df(company_message[0],company_message[4])
df4['姓名'] = df4['姓名'].apply(dedup_name)
# 对外投资
df5 = col_is_vertical_to_df(company_message[0],company_message[5])
# 变更记录
df6 = col_is_vertical_to_df(company_message[0],company_message[6])
# 检查目标xlsx是否存在,不存在则提前创建好
if not os.path.exists(save_path):
workbook = app.books.add()
workbook.save(save_path)
workbook.close()
time.sleep(1)
# 刚创建的时候,是空文件,重命名或新增对应工作表,进行第一次写入
wb = app.books.open(save_path)
wb.sheets[0].name = wb.sheets[0].name.replace('Sheet1','企业主页')
wb.sheets.add('工商信息',after='企业主页')
wb.sheets.add('股东信息',after='工商信息')
wb.sheets.add('主要人员',after='股东信息')
wb.sheets.add('对外投资',after='主要人员')
wb.sheets.add('变更记录',after='对外投资')
wb.save()
wb.close()
# 写数据进xlxs
wb_write = app.books.open(save_path)
regular_line(wb_write, '企业主页', df1)
regular_line(wb_write, '工商信息', df2)
Irregular_line(wb_write, '股东信息', df3)
Irregular_line(wb_write, '主要人员', df4)
Irregular_line(wb_write, '对外投资', df5)
Irregular_line(wb_write, '变更记录', df6)
wb_write.save()
wb_write.close()
time.sleep(1)
app.quit()
except Exception as e:
app.quit()
print(e)
至此,就可以得到这两家公司的一些详细信息。
ps:
412状态码
如果遇到412状态码,则表示网页怀疑你的程序是爬虫机器人,弹出了验证页面,但是由于我这是静态网页的爬取,无法做到验证,所以就被迫中断了,这个时候你可以点击该网页进入验证,又或者换ip,header,cookie等信息继续爬取。
如果大家在 soup.find_all(‘a’,{‘class’: ‘title’})[0].get(‘href’)这里遇到点错误,可能是天眼查那边更新了网页代码,大家可以根据这个操作来更新代码。
①按F12进入开发者调试页面
②就点击“深圳市腾讯计算机系统有限公司”这个点击操作而言,右击,然后选择“检查”选项,然后就可以看到开发者调试页面那里也自动跳转到了相关的位置。
③我们可以看到,这是一个a标签,class为title的html代码,所以,如果报错,可根据这个操作更换。比如,class改为了company_title,那代码也可对应的改为:soup.find_all(‘a’,{‘class’: ‘company_title’})[0].get(‘href’)
最后,大家需要注意的是,爬取的时候需要适当的设置一下睡眠时间,不然会被检测到是爬虫机器人在操作,可能会弹出弹窗让你验证,这样会导致循环被中断。第二个就是某个时间段爬取量尽量不要太大,不然也是会被检测到的。
由于完整代码太多了,我不想粘贴到博客上,大家都是程序员,可以去我的github上自取:https://github.com/guiyang-yang/notes/blob/main/spider/get_company_message.ipynb
参考文献:https://docs.xlwings.org/en/stable/quickstart.html
注明:转载需注明本原地址链接,利用代码进行非法行为与本人无关。